- Build execution is now entirely oriented around source distributions. - Make a lot of build plan objects and methods more agnostic to the build environment. This allows more flexibility and delays generation of some build metadata, such that we are able to more easily generate usage requirements data between libraries in the project.default_compile_flags
#include <dds/build.hpp> | #include <dds/build.hpp> | ||||
#include <dds/build/builder.hpp> | |||||
#include <dds/catalog/catalog.hpp> | #include <dds/catalog/catalog.hpp> | ||||
#include <dds/catalog/get.hpp> | #include <dds/catalog/get.hpp> | ||||
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
if (exists(man_filepath)) { | if (exists(man_filepath)) { | ||||
man = dds::package_manifest::load_from_file(man_filepath); | man = dds::package_manifest::load_from_file(man_filepath); | ||||
} | } | ||||
dds::builder bd; | |||||
dds::sdist_build_params main_params; | |||||
main_params.build_apps = !no_apps.Get(); | |||||
main_params.enable_warnings = !no_warnings.Get(); | |||||
main_params.run_tests = main_params.build_tests = !no_tests.Get(); | |||||
bd.add(dds::sdist{man, project.root.Get()}, main_params); | |||||
if (lm_index) { | if (lm_index) { | ||||
params.existing_lm_index = lm_index.Get(); | params.existing_lm_index = lm_index.Get(); | ||||
} else { | } else { | ||||
}) | }) | ||||
| ranges::to_vector; | | ranges::to_vector; | ||||
}); | }); | ||||
for (auto sd : params.dep_sdists) { | |||||
dds::sdist_build_params deps_params; | |||||
deps_params.subdir = dds::fs::path("_deps") / sd.manifest.pkg_id.to_string(); | |||||
bd.add(std::move(sd), deps_params); | |||||
} | |||||
} | } | ||||
dds::build(params, man); | |||||
bd.build(params); | |||||
return 0; | return 0; | ||||
} | } | ||||
}; | }; |
#include "./build.hpp" | #include "./build.hpp" | ||||
#include <dds/build/builder.hpp> | |||||
#include <dds/build/plan/compile_exec.hpp> | #include <dds/build/plan/compile_exec.hpp> | ||||
#include <dds/catch2_embedded.hpp> | #include <dds/catch2_embedded.hpp> | ||||
#include <dds/compdb.hpp> | #include <dds/compdb.hpp> | ||||
} | } | ||||
if (lib.create_archive()) { | if (lib.create_archive()) { | ||||
auto ar_path = lib.create_archive()->calc_archive_file_path(env); | |||||
auto ar_path | |||||
= env.output_root / lib.create_archive()->calc_archive_file_path(env.toolchain); | |||||
auto ar_dest = lib_out_root / ar_path.filename(); | auto ar_dest = lib_out_root / ar_path.filename(); | ||||
fs::create_directories(ar_dest.parent_path()); | fs::create_directories(ar_dest.parent_path()); | ||||
fs::copy_file(ar_path, ar_dest); | fs::copy_file(ar_path, ar_dest); | ||||
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, | |||||
build_env_ref env_) { | |||||
fs::path test_include_root = params.out_root / "_catch-2.10.2"; | |||||
lib_params.test_include_dirs.emplace_back(test_include_root); | |||||
auto catch_hpp = test_include_root / "catch2/catch.hpp"; | |||||
if (!fs::exists(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 | |||||
return; | |||||
} | |||||
std::string fname; | |||||
std::string definition; | |||||
if (test_driver == test_lib::catch_main) { | |||||
fname = "catch-main.cpp"; | |||||
definition = "CATCH_CONFIG_MAIN"; | |||||
} 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 << "#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 env2 = env_; | |||||
env2.output_root /= "_test-driver"; | |||||
auto obj_file = plan.calc_object_file_path(env2); | |||||
if (!fs::exists(obj_file)) { | |||||
spdlog::info("Compiling Catch2 test driver (This will only happen once)..."); | |||||
compile_all(std::array{plan}, env2, 1); | |||||
} | |||||
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, | |||||
build_env_ref env) { | |||||
auto& test_driver = *man.test_driver; | |||||
if (test_driver == test_lib::catch_ || test_driver == test_lib::catch_main) { | |||||
prepare_catch2_driver(lib_params, test_driver, params, env); | |||||
} else { | |||||
assert(false && "Unreachable"); | |||||
std::terminate(); | |||||
} | |||||
} | |||||
void add_ureqs(usage_requirement_map& ureqs, | void add_ureqs(usage_requirement_map& ureqs, | ||||
const sdist& sd, | const sdist& sd, | ||||
const library& lib, | const library& lib, | ||||
build_env_ref env) { | build_env_ref env) { | ||||
lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name); | lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name); | ||||
reqs.include_paths.push_back(lib.public_include_dir()); | reqs.include_paths.push_back(lib.public_include_dir()); | ||||
reqs.name = lib.manifest().name; | |||||
reqs.uses = lib.manifest().uses; | reqs.uses = lib.manifest().uses; | ||||
reqs.links = lib.manifest().links; | reqs.links = lib.manifest().links; | ||||
if (lib_plan.create_archive()) { | if (lib_plan.create_archive()) { | ||||
reqs.linkable_path = lib_plan.create_archive()->calc_archive_file_path(env); | |||||
reqs.linkable_path | |||||
= env.output_root / lib_plan.create_archive()->calc_archive_file_path(env.toolchain); | |||||
} | } | ||||
// TODO: preprocessor definitions | // TODO: preprocessor definitions | ||||
} | } | ||||
shared_compile_file_rules comp_rules = lib.base_compile_rules(); | shared_compile_file_rules comp_rules = lib.base_compile_rules(); | ||||
library_build_params lib_params; | library_build_params lib_params; | ||||
lib_params.out_subdir = fs::path("deps") / sd.manifest.pkg_id.name; | lib_params.out_subdir = fs::path("deps") / sd.manifest.pkg_id.name; | ||||
auto lib_plan = library_plan::create(lib, lib_params, ureqs); | |||||
auto lib_plan = library_plan::create(lib, lib_params); | |||||
// Create usage requirements for this libary. | // Create usage requirements for this libary. | ||||
add_ureqs(ureqs, sd, lib, lib_plan, env); | add_ureqs(ureqs, sd, lib, lib_plan, env); | ||||
// Add it to the plan: | // Add it to the plan: | ||||
void dds::build(const build_params& params, const package_manifest& man) { | void dds::build(const build_params& params, const package_manifest& man) { | ||||
fs::create_directories(params.out_root); | fs::create_directories(params.out_root); | ||||
auto db = database::open(params.out_root / ".dds.db"); | auto db = database::open(params.out_root / ".dds.db"); | ||||
dds::build_env env{params.toolchain, params.out_root, db}; | |||||
// The build plan we will fill out: | // The build plan we will fill out: | ||||
build_plan plan; | build_plan plan; | ||||
// Create a source distribution for the project, even if it doesn't have a manifest of its own | |||||
sdist root_sdist{man, params.root}; | |||||
// Collect libraries for the current project | // Collect libraries for the current project | ||||
auto libs = collect_libraries(params.root); | auto libs = collect_libraries(params.root); | ||||
if (!libs.size()) { | if (!libs.size()) { | ||||
usage_requirement_map ureqs; | usage_requirement_map ureqs; | ||||
dds::build_env env{params.toolchain, params.out_root, db, ureqs}; | |||||
if (params.existing_lm_index) { | if (params.existing_lm_index) { | ||||
ureqs = load_usage_requirements(params.root, params.out_root, *params.existing_lm_index); | ureqs = load_usage_requirements(params.root, params.out_root, *params.existing_lm_index); | ||||
} else { | } else { | ||||
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, env); | |||||
} | |||||
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)); | |||||
} | } | ||||
if (params.generate_compdb) { | if (params.generate_compdb) { |
#include "./builder.hpp" | |||||
#include <dds/build/plan/compile_exec.hpp> | |||||
#include <dds/build/plan/full.hpp> | |||||
#include <dds/catch2_embedded.hpp> | |||||
#include <dds/compdb.hpp> | |||||
#include <dds/usage_reqs.hpp> | |||||
#include <dds/util/time.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
#include <array> | |||||
#include <set> | |||||
using namespace dds; | |||||
namespace { | |||||
struct state { | |||||
bool generate_catch2_header = false; | |||||
bool generate_catch2_main = false; | |||||
}; | |||||
lm::library | |||||
prepare_catch2_driver(test_lib test_driver, const build_params& params, build_env_ref env_) { | |||||
fs::path test_include_root = params.out_root / "_catch-2.10.2"; | |||||
lm::library ret_lib; | |||||
auto catch_hpp = test_include_root / "catch2/catch.hpp"; | |||||
if (!fs::exists(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(); | |||||
} | |||||
ret_lib.include_paths.push_back(test_include_root); | |||||
if (test_driver == test_lib::catch_) { | |||||
// Don't compile a library helper | |||||
return ret_lib; | |||||
} | |||||
std::string fname; | |||||
std::string definition; | |||||
if (test_driver == test_lib::catch_main) { | |||||
fname = "catch-main.cpp"; | |||||
definition = "CATCH_CONFIG_MAIN"; | |||||
} 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 << "#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 env2 = env_; | |||||
env2.output_root /= "_test-driver"; | |||||
auto obj_file = plan.calc_object_file_path(env2); | |||||
if (!fs::exists(obj_file)) { | |||||
spdlog::info("Compiling Catch2 test driver (This will only happen once)..."); | |||||
compile_all(std::array{plan}, env2, 1); | |||||
} | |||||
ret_lib.linkable_path = obj_file; | |||||
return ret_lib; | |||||
} | |||||
lm::library | |||||
prepare_test_driver(const build_params& params, test_lib test_driver, build_env_ref env) { | |||||
if (test_driver == test_lib::catch_ || test_driver == test_lib::catch_main) { | |||||
return prepare_catch2_driver(test_driver, params, env); | |||||
} else { | |||||
assert(false && "Unreachable"); | |||||
std::terminate(); | |||||
} | |||||
} | |||||
library_plan prepare_library(state& st, | |||||
const sdist_target& sdt, | |||||
const library& lib, | |||||
const package_manifest& pkg_man) { | |||||
library_build_params lp; | |||||
lp.out_subdir = sdt.params.subdir; | |||||
lp.build_apps = sdt.params.build_apps; | |||||
lp.build_tests = sdt.params.build_tests; | |||||
lp.enable_warnings = sdt.params.enable_warnings; | |||||
if (lp.build_tests) { | |||||
if (pkg_man.test_driver == test_lib::catch_ | |||||
|| pkg_man.test_driver == test_lib::catch_main) { | |||||
lp.test_uses.push_back({".dds", "Catch"}); | |||||
st.generate_catch2_header = true; | |||||
if (pkg_man.test_driver == test_lib::catch_main) { | |||||
lp.test_uses.push_back({".dds", "Catch-Main"}); | |||||
st.generate_catch2_main = true; | |||||
} | |||||
} | |||||
} | |||||
return library_plan::create(lib, std::move(lp)); | |||||
} | |||||
package_plan prepare_one(state& st, const sdist_target& sd) { | |||||
package_plan pkg{sd.sd.manifest.pkg_id.name, sd.sd.manifest.namespace_}; | |||||
auto libs = collect_libraries(sd.sd.path); | |||||
for (const auto& lib : libs) { | |||||
pkg.add_library(prepare_library(st, sd, lib, sd.sd.manifest)); | |||||
} | |||||
return pkg; | |||||
} | |||||
build_plan prepare_build_plan(state& st, const std::vector<sdist_target>& sdists) { | |||||
build_plan plan; | |||||
for (const auto& sd_target : sdists) { | |||||
plan.add_package(prepare_one(st, sd_target)); | |||||
} | |||||
return plan; | |||||
} | |||||
usage_requirement_map | |||||
prepare_ureqs(const build_plan& plan, const toolchain& toolchain, path_ref out_root) { | |||||
usage_requirement_map ureqs; | |||||
for (const auto& pkg : plan.packages()) { | |||||
for (const auto& lib : pkg.libraries()) { | |||||
auto& lib_reqs = ureqs.add(pkg.namespace_(), lib.name()); | |||||
lib_reqs.include_paths.push_back(lib.library_().public_include_dir()); | |||||
lib_reqs.uses = lib.library_().manifest().uses; | |||||
lib_reqs.links = lib.library_().manifest().links; | |||||
if (const auto& arc = lib.create_archive()) { | |||||
lib_reqs.linkable_path = out_root / arc->calc_archive_file_path(toolchain); | |||||
} | |||||
} | |||||
} | |||||
return ureqs; | |||||
} | |||||
} // namespace | |||||
void builder::build(const build_params& params) const { | |||||
fs::create_directories(params.out_root); | |||||
auto db = database::open(params.out_root / ".dds.db"); | |||||
state st; | |||||
auto plan = prepare_build_plan(st, _sdists); | |||||
auto ureqs = prepare_ureqs(plan, params.toolchain, params.out_root); | |||||
build_env env{params.toolchain, params.out_root, db, ureqs}; | |||||
if (st.generate_catch2_main) { | |||||
auto catch_lib = prepare_test_driver(params, test_lib::catch_main, env); | |||||
ureqs.add(".dds", "Catch-Main") = catch_lib; | |||||
} | |||||
if (st.generate_catch2_header) { | |||||
auto catch_lib = prepare_test_driver(params, test_lib::catch_, env); | |||||
ureqs.add(".dds", "Catch") = catch_lib; | |||||
} | |||||
if (params.generate_compdb) { | |||||
generate_compdb(plan, env); | |||||
} | |||||
dds::stopwatch sw; | |||||
plan.compile_all(env, params.parallel_jobs); | |||||
spdlog::info("Compilation completed in {:n}ms", sw.elapsed_ms().count()); | |||||
sw.reset(); | |||||
plan.archive_all(env, params.parallel_jobs); | |||||
spdlog::info("Archiving completed in {:n}ms", sw.elapsed_ms().count()); | |||||
if (params.build_tests || params.build_apps) { | |||||
sw.reset(); | |||||
plan.link_all(env, params.parallel_jobs); | |||||
spdlog::info("Runtime binary linking completed in {:n}ms", sw.elapsed_ms().count()); | |||||
} | |||||
if (params.build_tests) { | |||||
sw.reset(); | |||||
auto test_failures = plan.run_all_tests(env, params.parallel_jobs); | |||||
spdlog::info("Test execution finished in {:n}ms", sw.elapsed_ms().count()); | |||||
for (auto& failures : test_failures) { | |||||
spdlog::error("Test {} failed! Output:\n{}[dds - test output end]", | |||||
failures.executable_path.string(), | |||||
failures.output); | |||||
} | |||||
if (!test_failures.empty()) { | |||||
throw compile_failure("Test failures during the build!"); | |||||
} | |||||
} | |||||
} |
#pragma once | |||||
#include <dds/build/params.hpp> | |||||
#include <dds/source/dist.hpp> | |||||
#include <cassert> | |||||
#include <map> | |||||
namespace dds { | |||||
/** | |||||
* Parameters for building an individual source distribution as part of a larger build plan. | |||||
*/ | |||||
struct sdist_build_params { | |||||
/// The subdirectory in which the source directory should be built | |||||
fs::path subdir; | |||||
/// Whether to build tests | |||||
bool build_tests = false; | |||||
/// Whether to run tests | |||||
bool run_tests = false; | |||||
/// Whether to build applications | |||||
bool build_apps = false; | |||||
/// Whether to enable build warnings | |||||
bool enable_warnings = false; | |||||
}; | |||||
/** | |||||
* Just a pairing of an sdist to the parameters that are used to build it. | |||||
*/ | |||||
struct sdist_target { | |||||
/// The source distribution | |||||
sdist sd; | |||||
/// The build parameters thereof | |||||
sdist_build_params params; | |||||
}; | |||||
/** | |||||
* A builder object. Source distributions are added to the builder, and then they are all built in parallel via `build()` | |||||
*/ | |||||
class builder { | |||||
/// Source distributions that have been added | |||||
std::vector<sdist_target> _sdists; | |||||
public: | |||||
/// Add more source distributions | |||||
void add(sdist sd) { add(std::move(sd), sdist_build_params()); } | |||||
void add(sdist sd, sdist_build_params params) { | |||||
_sdists.push_back({std::move(sd), std::move(params)}); | |||||
} | |||||
/** | |||||
* Execute the build | |||||
*/ | |||||
void build(const build_params& params) const; | |||||
}; | |||||
} // namespace dds |
using namespace dds; | using namespace dds; | ||||
fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept { | |||||
return env.output_root / _subdir | |||||
/ fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix()); | |||||
fs::path create_archive_plan::calc_archive_file_path(const toolchain& tc) const noexcept { | |||||
return _subdir / fmt::format("{}{}{}", "lib", _name, tc.archive_suffix()); | |||||
} | } | ||||
void create_archive_plan::archive(const build_env& env) const { | void create_archive_plan::archive(const build_env& env) const { | ||||
// Build up the archive command | // Build up the archive command | ||||
archive_spec ar; | archive_spec ar; | ||||
ar.input_files = std::move(objects); | ar.input_files = std::move(objects); | ||||
ar.out_path = calc_archive_file_path(env); | |||||
ar.out_path = env.output_root / calc_archive_file_path(env.toolchain); | |||||
auto ar_cmd = env.toolchain.create_archive_command(ar); | auto ar_cmd = env.toolchain.create_archive_command(ar); | ||||
// `out_relpath` is purely for the benefit of the user to have a short name | // `out_relpath` is purely for the benefit of the user to have a short name |
const std::string& name() const noexcept { return _name; } | const std::string& name() const noexcept { return _name; } | ||||
/** | /** | ||||
* Calculate the absolute path where the generated archive libary file will | |||||
* be generated after execution. | |||||
* @param env The build environment for the archival. | |||||
* Calculate the path relative to the build output root where the static library archive will be | |||||
* placed upon creation. | |||||
* @param tc The toolchain that will be used | |||||
*/ | */ | ||||
fs::path calc_archive_file_path(build_env_ref env) const noexcept; | |||||
fs::path calc_archive_file_path(const toolchain& tc) const noexcept; | |||||
/** | /** | ||||
* Get the compilation plans for this library. | * Get the compilation plans for this library. |
#include <dds/db/database.hpp> | #include <dds/db/database.hpp> | ||||
#include <dds/toolchain/toolchain.hpp> | #include <dds/toolchain/toolchain.hpp> | ||||
#include <dds/usage_reqs.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
namespace dds { | namespace dds { | ||||
dds::toolchain toolchain; | dds::toolchain toolchain; | ||||
fs::path output_root; | fs::path output_root; | ||||
database& db; | database& db; | ||||
const usage_requirement_map& ureqs; | |||||
}; | }; | ||||
using build_env_ref = const build_env&; | using build_env_ref = const build_env&; |
compile_file_spec spec{_source.path, calc_object_file_path(env)}; | compile_file_spec spec{_source.path, calc_object_file_path(env)}; | ||||
spec.enable_warnings = _rules.enable_warnings(); | spec.enable_warnings = _rules.enable_warnings(); | ||||
extend(spec.include_dirs, _rules.include_dirs()); | extend(spec.include_dirs, _rules.include_dirs()); | ||||
for (const auto& use : _rules.uses()) { | |||||
extend(spec.include_dirs, env.ureqs.include_paths(use)); | |||||
} | |||||
extend(spec.definitions, _rules.defs()); | extend(spec.definitions, _rules.defs()); | ||||
return env.toolchain.create_compile_command(spec); | return env.toolchain.create_compile_command(spec); | ||||
} | } |
#include <dds/build/plan/base.hpp> | #include <dds/build/plan/base.hpp> | ||||
#include <dds/source/file.hpp> | #include <dds/source/file.hpp> | ||||
#include <libman/library.hpp> | |||||
#include <memory> | #include <memory> | ||||
namespace dds { | namespace dds { | ||||
struct rules_impl { | struct rules_impl { | ||||
std::vector<fs::path> inc_dirs; | std::vector<fs::path> inc_dirs; | ||||
std::vector<std::string> defs; | std::vector<std::string> defs; | ||||
std::vector<lm::usage> uses; | |||||
bool enable_warnings = false; | bool enable_warnings = false; | ||||
}; | }; | ||||
auto& defs() noexcept { return _impl->defs; } | auto& defs() noexcept { return _impl->defs; } | ||||
auto& defs() const noexcept { return _impl->defs; } | auto& defs() const noexcept { return _impl->defs; } | ||||
/** | |||||
* Access the named usage requirements for this set of rules | |||||
*/ | |||||
auto& uses() noexcept { return _impl->uses; } | |||||
auto& uses() const noexcept { return _impl->uses; } | |||||
/** | /** | ||||
* A boolean to toggle compile warnings for the associated compiles | * A boolean to toggle compile warnings for the associated compiles | ||||
*/ | */ |
#include <dds/build/plan/library.hpp> | #include <dds/build/plan/library.hpp> | ||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <dds/util/algo.hpp> | |||||
#include <dds/util/time.hpp> | #include <dds/util/time.hpp> | ||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
link_exe_spec spec; | link_exe_spec spec; | ||||
spec.output = calc_executable_path(env); | spec.output = calc_executable_path(env); | ||||
spec.inputs = _input_libs; | spec.inputs = _input_libs; | ||||
for (const lm::usage& links : _links) { | |||||
extend(spec.inputs, env.ureqs.link_paths(links)); | |||||
} | |||||
if (lib.create_archive()) { | if (lib.create_archive()) { | ||||
// The associated library has compiled components. Add the static library a as a linker | // The associated library has compiled components. Add the static library a as a linker | ||||
// input | // input | ||||
spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env)); | |||||
spec.inputs.push_back(env.output_root | |||||
/ lib.create_archive()->calc_archive_file_path(env.toolchain)); | |||||
} | } | ||||
// The main object should be a linker input, of course. | // The main object should be a linker input, of course. |
#include <dds/build/plan/compile_file.hpp> | #include <dds/build/plan/compile_file.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <libman/library.hpp> | |||||
#include <string> | #include <string> | ||||
#include <vector> | #include <vector> | ||||
class link_executable_plan { | class link_executable_plan { | ||||
/// The linker inputs that should be linked into the executable | /// The linker inputs that should be linked into the executable | ||||
std::vector<fs::path> _input_libs; | std::vector<fs::path> _input_libs; | ||||
/// Usage requirements for this executable | |||||
std::vector<lm::usage> _links; | |||||
/// The compilation plan for the entry-point source file | /// The compilation plan for the entry-point source file | ||||
compile_file_plan _main_compile; | compile_file_plan _main_compile; | ||||
/// The subdirectory in which the executable should be generated | /// The subdirectory in which the executable should be generated | ||||
/** | /** | ||||
* Create a new instance | * Create a new instance | ||||
* @param in_libs Linker inputs for the executable | * @param in_libs Linker inputs for the executable | ||||
* @param links The library identifiers that the executable should link with | |||||
* @param cfp The file compilation that defines the entrypoint of the application | * @param cfp The file compilation that defines the entrypoint of the application | ||||
* @param out_subdir The subdirectory of the build root in which the executable should be placed | * @param out_subdir The subdirectory of the build root in which the executable should be placed | ||||
* @param name_ The name of the executable | * @param name_ The name of the executable | ||||
*/ | */ | ||||
link_executable_plan(std::vector<fs::path> in_libs, | |||||
compile_file_plan cfp, | |||||
path_ref out_subdir, | |||||
std::string name_) | |||||
link_executable_plan(std::vector<fs::path> in_libs, | |||||
std::vector<lm::usage> links, | |||||
compile_file_plan cfp, | |||||
path_ref out_subdir, | |||||
std::string name_) | |||||
: _input_libs(std::move(in_libs)) | : _input_libs(std::move(in_libs)) | ||||
, _links(std::move(links)) | |||||
, _main_compile(std::move(cfp)) | , _main_compile(std::move(cfp)) | ||||
, _out_subdir(out_subdir) | , _out_subdir(out_subdir) | ||||
, _name(std::move(name_)) {} | , _name(std::move(name_)) {} |
using namespace dds; | using namespace dds; | ||||
library_plan library_plan::create(const library& lib, | |||||
const library_build_params& params, | |||||
const usage_requirement_map& ureqs) { | |||||
library_plan library_plan::create(const library& lib, const library_build_params& params) { | |||||
// Source files are kept in three groups: | // Source files are kept in three groups: | ||||
std::vector<source_file> app_sources; | std::vector<source_file> app_sources; | ||||
std::vector<source_file> test_sources; | std::vector<source_file> test_sources; | ||||
// Load up the compile rules | // Load up the compile rules | ||||
auto compile_rules = lib.base_compile_rules(); | auto compile_rules = lib.base_compile_rules(); | ||||
compile_rules.enable_warnings() = params.enable_warnings; | compile_rules.enable_warnings() = params.enable_warnings; | ||||
// Apply our transitive usage requirements. This gives us the search directories for our | |||||
// dependencies. | |||||
for (const auto& use : lib.manifest().uses) { | |||||
ureqs.apply(compile_rules, use.namespace_, use.name); | |||||
} | |||||
compile_rules.uses() = lib.manifest().uses; | |||||
// Convert the library sources into their respective file compilation plans. | // Convert the library sources into their respective file compilation plans. | ||||
auto lib_compile_files = // | auto lib_compile_files = // | ||||
// Collect the paths to linker inputs that should be used when generating executables for this | // Collect the paths to linker inputs that should be used when generating executables for this | ||||
// library. | // library. | ||||
std::vector<fs::path> link_libs; | |||||
for (auto& use : lib.manifest().uses) { | |||||
extend(link_libs, ureqs.link_paths(use.namespace_, use.name)); | |||||
} | |||||
for (auto& link : lib.manifest().links) { | |||||
extend(link_libs, ureqs.link_paths(link.namespace_, link.name)); | |||||
} | |||||
std::vector<lm::usage> links; | |||||
extend(links, lib.manifest().uses); | |||||
extend(links, lib.manifest().links); | |||||
// Linker inputs for tests may contain additional code for test execution | // Linker inputs for tests may contain additional code for test execution | ||||
auto test_link_libs = link_libs; | |||||
extend(test_link_libs, params.test_link_files); | |||||
std::vector<fs::path> link_libs; | |||||
std::vector<fs::path> test_link_libs = params.test_link_files; | |||||
// There may also be additional #include paths for test source files | |||||
// There may also be additional usage requirements for tests | |||||
auto test_rules = compile_rules.clone(); | auto test_rules = compile_rules.clone(); | ||||
extend(test_rules.include_dirs(), params.test_include_dirs); | |||||
auto test_links = links; | |||||
extend(test_rules.uses(), params.test_uses); | |||||
extend(test_links, params.test_uses); | |||||
// Generate the plans to link any executables for this library | // Generate the plans to link any executables for this library | ||||
std::vector<link_executable_plan> link_executables; | std::vector<link_executable_plan> link_executables; | ||||
auto rules = is_test ? test_rules : compile_rules; | auto rules = is_test ? test_rules : compile_rules; | ||||
// Pick input libs based on app/test | // Pick input libs based on app/test | ||||
auto& exe_link_libs = is_test ? test_link_libs : link_libs; | auto& exe_link_libs = is_test ? test_link_libs : link_libs; | ||||
auto& exe_links = is_test ? test_links : links; | |||||
// TODO: Apps/tests should only see the _public_ include dir, not both | // 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"), | |||||
subdir, | |||||
source.path.stem().stem().string()); | |||||
auto exe = link_executable_plan{exe_link_libs, | |||||
exe_links, | |||||
compile_file_plan(rules, | |||||
source, | |||||
lib.manifest().name, | |||||
params.out_subdir / "obj"), | |||||
subdir, | |||||
source.path.stem().stem().string()}; | |||||
link_executables.emplace_back(std::move(exe)); | |||||
} | } | ||||
// Done! | // Done! | ||||
return library_plan{lib.manifest().name, | |||||
lib.path(), | |||||
std::move(create_archive), | |||||
std::move(link_executables), | |||||
lib.manifest().uses, | |||||
lib.manifest().links}; | |||||
} | |||||
return library_plan{lib, std::move(create_archive), std::move(link_executables)}; | |||||
} |
#include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <libman/library.hpp> | |||||
#include <optional> | #include <optional> | ||||
#include <string> | #include <string> | ||||
#include <vector> | #include <vector> | ||||
std::vector<fs::path> test_include_dirs; | std::vector<fs::path> test_include_dirs; | ||||
/// Files that should be added as inputs when linking test executables | /// Files that should be added as inputs when linking test executables | ||||
std::vector<fs::path> test_link_files; | std::vector<fs::path> test_link_files; | ||||
/// Libraries that are used by tests | |||||
std::vector<lm::usage> test_uses; | |||||
}; | }; | ||||
/** | /** | ||||
* initialize all of the constructor parameters correctly. | * initialize all of the constructor parameters correctly. | ||||
*/ | */ | ||||
class library_plan { | class library_plan { | ||||
/// The name of the library | |||||
std::string _name; | |||||
/// The directory at the root of this library | |||||
fs::path _source_root; | |||||
/// The underlying library object | |||||
library _lib; | |||||
/// The `create_archive_plan` for this library, if applicable | /// The `create_archive_plan` for this library, if applicable | ||||
std::optional<create_archive_plan> _create_archive; | std::optional<create_archive_plan> _create_archive; | ||||
/// The executables that should be linked as part of this library's build | /// The executables that should be linked as part of this library's build | ||||
std::vector<link_executable_plan> _link_exes; | std::vector<link_executable_plan> _link_exes; | ||||
/// The libraries that we use | |||||
std::vector<lm::usage> _uses; | |||||
/// The libraries that we link | |||||
std::vector<lm::usage> _links; | |||||
public: | public: | ||||
/** | /** | ||||
* Construct a new `library_plan` | * Construct a new `library_plan` | ||||
* @param name The name of the library | |||||
* @param source_root The directory that contains this library | |||||
* @param lib The `library` object underlying this plan. | |||||
* @param ar The `create_archive_plan`, or `nullopt` for this library. | * @param ar The `create_archive_plan`, or `nullopt` for this library. | ||||
* @param exes The `link_executable_plan` objects for this library. | * @param exes The `link_executable_plan` objects for this library. | ||||
* @param uses The identities of the libraries that are used by this library | |||||
* @param links The identities of the libraries that are linked by this library | |||||
*/ | */ | ||||
library_plan(std::string_view name, | |||||
path_ref source_root, | |||||
library_plan(library lib, | |||||
std::optional<create_archive_plan> ar, | std::optional<create_archive_plan> ar, | ||||
std::vector<link_executable_plan> exes, | |||||
std::vector<lm::usage> uses, | |||||
std::vector<lm::usage> links) | |||||
: _name(name) | |||||
, _source_root(source_root) | |||||
std::vector<link_executable_plan> exes) | |||||
: _lib(std::move(lib)) | |||||
, _create_archive(std::move(ar)) | , _create_archive(std::move(ar)) | ||||
, _link_exes(std::move(exes)) | |||||
, _uses(std::move(uses)) | |||||
, _links(std::move(links)) {} | |||||
, _link_exes(std::move(exes)) {} | |||||
/** | |||||
* Get the underlying library object | |||||
*/ | |||||
auto& library_() const noexcept { return _lib; } | |||||
/** | /** | ||||
* Get the name of the library | * Get the name of the library | ||||
*/ | */ | ||||
auto& name() const noexcept { return _name; } | |||||
auto& name() const noexcept { return _lib.manifest().name; } | |||||
/** | /** | ||||
* The directory that defines the source root of the library. | * The directory that defines the source root of the library. | ||||
*/ | */ | ||||
path_ref source_root() const noexcept { return _source_root; } | |||||
path_ref source_root() const noexcept { return _lib.path(); } | |||||
/** | /** | ||||
* A `create_archive_plan` object, or `nullopt`, depending on if this library has compiled | * A `create_archive_plan` object, or `nullopt`, depending on if this library has compiled | ||||
* components | * components | ||||
/** | /** | ||||
* The library identifiers that are used by this library | * The library identifiers that are used by this library | ||||
*/ | */ | ||||
auto& uses() const noexcept { return _uses; } | |||||
auto& uses() const noexcept { return _lib.manifest().uses; } | |||||
/** | /** | ||||
* The library identifiers that are linked by this library | * The library identifiers that are linked by this library | ||||
*/ | */ | ||||
auto& links() const noexcept { return _links; } | |||||
auto& links() const noexcept { return _lib.manifest().links; } | |||||
/** | /** | ||||
* Named constructor: Create a new `library_plan` automatically from some build-time parameters. | * Named constructor: Create a new `library_plan` automatically from some build-time parameters. | ||||
* @param lib The `library` object from which we will inherit several properties. | * @param lib The `library` object from which we will inherit several properties. | ||||
* @param params Parameters controlling the build of the library. i.e. if we create tests, | * @param params Parameters controlling the build of the library. i.e. if we create tests, | ||||
* enable warnings, etc. | * enable warnings, etc. | ||||
* @param ureqs The usage requirements map. This should be populated as appropriate. | |||||
* | * | ||||
* The `lib` parameter defines the usage requirements of this library, and they are looked up in | * The `lib` parameter defines the usage requirements of this library, and they are looked up in | ||||
* the `ureqs` map. If there are any missing requirements, an exception will be thrown. | * the `ureqs` map. If there are any missing requirements, an exception will be thrown. | ||||
*/ | */ | ||||
static library_plan create(const library& lib, | |||||
const library_build_params& params, | |||||
const usage_requirement_map& ureqs); | |||||
static library_plan create(const library& lib, const library_build_params& params); | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
using namespace dds; | using namespace dds; | ||||
const lm::library* usage_requirement_map::get(std::string ns, std::string name) const noexcept { | |||||
auto found = _reqs.find(library_key{ns, name}); | |||||
const lm::library* usage_requirement_map::get(const lm::usage& key) const noexcept { | |||||
auto found = _reqs.find(key); | |||||
if (found == _reqs.end()) { | if (found == _reqs.end()) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return ret; | return ret; | ||||
} | } | ||||
std::vector<fs::path> usage_requirement_map::link_paths(std::string ns, std::string name) const { | |||||
auto req = get(ns, name); | |||||
std::vector<fs::path> usage_requirement_map::link_paths(const lm::usage& key) const { | |||||
auto req = get(key); | |||||
if (!req) { | if (!req) { | ||||
throw std::runtime_error( | throw std::runtime_error( | ||||
fmt::format("Unable to find linking requirement '{}/{}'", ns, name)); | |||||
fmt::format("Unable to find linking requirement '{}/{}'", key.namespace_, key.name)); | |||||
} | } | ||||
std::vector<fs::path> ret; | std::vector<fs::path> ret; | ||||
if (req->linkable_path) { | if (req->linkable_path) { | ||||
ret.push_back(*req->linkable_path); | ret.push_back(*req->linkable_path); | ||||
} | } | ||||
for (const auto& dep : req->uses) { | for (const auto& dep : req->uses) { | ||||
extend(ret, link_paths(dep.namespace_, dep.name)); | |||||
extend(ret, link_paths(dep)); | |||||
} | } | ||||
for (const auto& link : req->links) { | for (const auto& link : req->links) { | ||||
extend(ret, link_paths(link.namespace_, link.name)); | |||||
extend(ret, link_paths(link)); | |||||
} | |||||
return ret; | |||||
} | |||||
std::vector<fs::path> usage_requirement_map::include_paths(const lm::usage& usage) const { | |||||
std::vector<fs::path> ret; | |||||
auto lib = get(usage.namespace_, usage.name); | |||||
if (!lib) { | |||||
throw std::runtime_error( | |||||
fmt::format("Cannot find non-existent usage requirements for '{}/{}'", | |||||
usage.namespace_, | |||||
usage.name)); | |||||
} | |||||
extend(ret, lib->include_paths); | |||||
for (const auto& transitive : lib->uses) { | |||||
extend(ret, include_paths(transitive)); | |||||
} | } | ||||
return ret; | return ret; | ||||
} | } |
class usage_requirement_map { | class usage_requirement_map { | ||||
struct library_key { | |||||
std::string namespace_; | |||||
std::string name; | |||||
}; | |||||
using library_key = lm::usage; | |||||
struct library_key_compare { | struct library_key_compare { | ||||
bool operator()(const library_key& lhs, const library_key& rhs) const noexcept { | bool operator()(const library_key& lhs, const library_key& rhs) const noexcept { | ||||
std::map<library_key, lm::library, library_key_compare> _reqs; | std::map<library_key, lm::library, library_key_compare> _reqs; | ||||
public: | public: | ||||
const lm::library* get(std::string ns, std::string name) const noexcept; | |||||
const lm::library* get(const lm::usage& key) const noexcept; | |||||
const lm::library* get(std::string ns, std::string name) const noexcept { | |||||
return get({ns, name}); | |||||
} | |||||
lm::library& add(std::string ns, std::string name); | lm::library& add(std::string ns, std::string name); | ||||
void add(std::string ns, std::string name, lm::library lib) { add(ns, name) = lib; } | void add(std::string ns, std::string name, lm::library lib) { add(ns, name) = lib; } | ||||
void apply(shared_compile_file_rules, std::string ns, std::string name) const; | void apply(shared_compile_file_rules, std::string ns, std::string name) const; | ||||
std::vector<fs::path> link_paths(std::string ns, std::string name) const; | |||||
std::vector<fs::path> link_paths(const lm::usage&) const; | |||||
std::vector<fs::path> include_paths(const lm::usage& req) const; | |||||
static usage_requirement_map from_lm_index(const lm::index&) noexcept; | static usage_requirement_map from_lm_index(const lm::index&) noexcept; | ||||
}; | }; |