#include "./build.hpp" | #include "./build.hpp" | ||||
#include <dds/build/compile.hpp> | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <dds/project.hpp> | #include <dds/project.hpp> | ||||
#include <dds/source.hpp> | #include <dds/source.hpp> | ||||
#include <dds/toolchain.hpp> | #include <dds/toolchain.hpp> | ||||
#include <dds/usage_reqs.hpp> | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <libman/index.hpp> | #include <libman/index.hpp> | ||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <range/v3/action/join.hpp> | |||||
#include <range/v3/algorithm/for_each.hpp> | |||||
#include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
#include <range/v3/view/drop.hpp> | |||||
#include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
#include <range/v3/view/join.hpp> | |||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
#include <algorithm> | #include <algorithm> | ||||
} | } | ||||
for (const auto& use : lib.manifest().uses) { | for (const auto& use : lib.manifest().uses) { | ||||
pairs.emplace_back("Uses", use); | |||||
pairs.emplace_back("Uses", fmt::format("{}/{}", use.namespace_, use.name)); | |||||
} | } | ||||
for (const auto& links : lib.manifest().links) { | for (const auto& links : lib.manifest().links) { | ||||
pairs.emplace_back("Links", links); | |||||
pairs.emplace_back("Links", fmt::format("{}/{}", links.namespace_, links.name)); | |||||
} | } | ||||
lm::write_pairs(lml_path, pairs); | lm::write_pairs(lml_path, pairs); | ||||
lm::write_pairs(export_root / "package.lmp", pairs); | lm::write_pairs(export_root / "package.lmp", pairs); | ||||
} | } | ||||
void include_deps(const lm::index::library_index& lib_index, | |||||
std::vector<fs::path>& includes, | |||||
std::vector<std::string>& defines, | |||||
std::string_view usage_key, | |||||
bool is_public_usage) { | |||||
auto pair = split(usage_key, "/"); | |||||
if (pair.size() != 2) { | |||||
throw compile_failure(fmt::format("Invalid `Uses`: {}", usage_key)); | |||||
} | |||||
auto& pkg_ns = pair[0]; | |||||
auto& lib = pair[1]; | |||||
auto found = lib_index.find(std::pair(pkg_ns, lib)); | |||||
if (found == lib_index.end()) { | |||||
throw compile_failure( | |||||
fmt::format("No library '{}/{}': Check that it is installed and available", | |||||
pkg_ns, | |||||
lib)); | |||||
} | |||||
const lm::library& lm_lib = found->second; | |||||
extend(includes, lm_lib.include_paths); | |||||
extend(defines, lm_lib.preproc_defs); | |||||
for (const auto& uses : lm_lib.uses) { | |||||
include_deps(lib_index, includes, defines, uses, is_public_usage && true); | |||||
} | |||||
for (const auto& links : lm_lib.links) { | |||||
include_deps(lib_index, includes, defines, links, false); | |||||
} | |||||
} | |||||
std::vector<compile_file_plan> file_compilations_of_lib(const build_params& params, | |||||
const library& lib) { | |||||
const auto& sources = lib.all_sources(); | |||||
std::vector<fs::path> dep_includes; | |||||
std::vector<std::string> dep_defines; | |||||
if (!lib.manifest().uses.empty() || !lib.manifest().links.empty()) { | |||||
fs::path lm_index_path = params.lm_index; | |||||
for (auto cand : {fs::path("INDEX.lmi"), params.out_root / "INDEX.lmi"}) { | |||||
if (fs::exists(lm_index_path)) { | |||||
break; | |||||
} | |||||
lm_index_path = params.root / cand; | |||||
usage_requirement_map | |||||
load_usage_requirements(path_ref project_root, path_ref build_root, path_ref user_lm_index) { | |||||
fs::path lm_index_path = user_lm_index; | |||||
for (auto cand : {project_root / "INDEX.lmi", build_root / "INDEX.lmi"}) { | |||||
if (fs::exists(lm_index_path) || !user_lm_index.empty()) { | |||||
break; | |||||
} | } | ||||
if (!fs::exists(lm_index_path)) { | |||||
throw compile_failure( | |||||
"No `INDEX.lmi` found, but we need to pull in dependencies." | |||||
"Use a package manager to generate an INDEX.lmi"); | |||||
} | |||||
auto lm_index = lm::index::from_file(lm_index_path); | |||||
auto lib_index = lm_index.build_library_index(); | |||||
for (const auto& uses : lib.manifest().uses) { | |||||
include_deps(lib_index, dep_includes, dep_defines, uses, true); | |||||
} | |||||
for (const auto& links : lib.manifest().links) { | |||||
include_deps(lib_index, dep_includes, dep_defines, links, false); | |||||
} | |||||
spdlog::critical("Dependency resolution isn't fully implemented yet!!"); | |||||
lm_index_path = cand; | |||||
} | } | ||||
if (sources.empty()) { | |||||
spdlog::info("No source files found to compile"); | |||||
if (!fs::exists(lm_index_path)) { | |||||
spdlog::warn("No INDEX.lmi found, so we won't be able to load/use any dependencies"); | |||||
return {}; | |||||
} | } | ||||
auto should_compile_source = [&](const source_file& sf) { | |||||
return (sf.kind == source_kind::source || (sf.kind == source_kind::app && params.build_apps) | |||||
|| (sf.kind == source_kind::test && params.build_tests)); | |||||
}; | |||||
shared_compile_file_rules rules; | |||||
extend(rules.defs(), lib.manifest().private_defines); | |||||
extend(rules.defs(), dep_defines); | |||||
extend(rules.include_dirs(), lib.manifest().private_includes); | |||||
extend(rules.include_dirs(), dep_includes); | |||||
rules.include_dirs().push_back(fs::absolute(lib.path() / "src")); | |||||
rules.include_dirs().push_back(fs::absolute(lib.path() / "include")); | |||||
rules.enable_warnings() = params.enable_warnings; | |||||
return // | |||||
sources // | |||||
| filter(should_compile_source) // | |||||
| transform([&](auto&& src) { | |||||
return compile_file_plan{rules, | |||||
"obj/" + lib.manifest().name, | |||||
src, | |||||
lib.manifest().name}; | |||||
}) // | |||||
| to_vector; | |||||
lm::index idx = lm::index::from_file(lm_index_path); | |||||
return usage_requirement_map::from_lm_index(idx); | |||||
} | } | ||||
std::vector<dds::compile_file_plan> collect_compiles(const build_params& params, | |||||
const project& project) { | |||||
auto libs = iter_libraries(project); | |||||
return // | |||||
libs // | |||||
| transform([&](auto&& lib) { return file_compilations_of_lib(params, lib); }) // | |||||
| ranges::actions::join // | |||||
| to_vector // | |||||
; | |||||
} | |||||
using object_file_index = std::map<fs::path, fs::path>; | |||||
/** | |||||
* Obtain the path to the object file that corresponds to the named source file | |||||
*/ | |||||
fs::path obj_for_source(const object_file_index& idx, path_ref source_path) { | |||||
auto iter = idx.find(source_path); | |||||
if (iter == idx.end()) { | |||||
assert(false && "Lookup on invalid source file"); | |||||
std::terminate(); | |||||
} | |||||
return iter->second; | |||||
} | |||||
/** | |||||
* Create the static library archive for the given library object. | |||||
*/ | |||||
std::optional<fs::path> create_lib_archive(const build_params& params, | |||||
const library& lib, | |||||
const object_file_index& obj_idx) { | |||||
archive_spec arc; | |||||
arc.out_path = lib_archive_path(params, lib); | |||||
// Collect object files that make up that library | |||||
arc.input_files = // | |||||
lib.all_sources() // | |||||
| filter([](auto&& s) { return s.kind == source_kind::source; }) // | |||||
| transform([&](auto&& s) { return obj_for_source(obj_idx, s.path); }) // | |||||
| to_vector // | |||||
; | |||||
if (arc.input_files.empty()) { | |||||
return std::nullopt; | |||||
} | |||||
auto ar_cmd = params.toolchain.create_archive_command(arc); | |||||
if (fs::exists(arc.out_path)) { | |||||
fs::remove(arc.out_path); | |||||
} | |||||
spdlog::info("Create archive for {}: {}", lib.manifest().name, arc.out_path.string()); | |||||
fs::create_directories(arc.out_path.parent_path()); | |||||
auto ar_res = run_proc(ar_cmd); | |||||
if (!ar_res.okay()) { | |||||
spdlog::error("Failure creating archive library {}", arc.out_path); | |||||
spdlog::error("Subcommand failed: {}", quote_command(ar_cmd)); | |||||
spdlog::error("Subcommand produced output:\n{}", ar_res.output); | |||||
throw archive_failure("Failed to create the library archive"); | |||||
} | |||||
return arc.out_path; | |||||
} | |||||
/** | |||||
* Link a single test executable identified by a single source file | |||||
*/ | |||||
fs::path link_one_exe(path_ref dest, | |||||
path_ref source_file, | |||||
const build_params& params, | |||||
const library& lib, | |||||
const object_file_index& obj_idx) { | |||||
auto main_obj = obj_for_source(obj_idx, source_file); | |||||
assert(fs::exists(main_obj)); | |||||
link_exe_spec spec; | |||||
spec.inputs.push_back(main_obj); | |||||
auto lib_arc_path = lib_archive_path(params, lib); | |||||
if (fs::is_regular_file(lib_arc_path)) { | |||||
spec.inputs.push_back(lib_arc_path); | |||||
} | |||||
spec.output = dest; | |||||
const auto link_command = params.toolchain.create_link_executable_command(spec); | |||||
} // namespace | |||||
spdlog::info("Create executable: {}", (fs::relative(spec.output, params.out_root)).string()); | |||||
fs::create_directories(spec.output.parent_path()); | |||||
auto proc_res = run_proc(link_command); | |||||
if (!proc_res.okay()) { | |||||
throw compile_failure( | |||||
fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | |||||
spec.output.string(), | |||||
quote_command(link_command), | |||||
proc_res.retc, | |||||
proc_res.output)); | |||||
void dds::build(const build_params& params, const package_manifest& man) { | |||||
auto libs = collect_libraries(params.root); | |||||
if (!libs.size()) { | |||||
spdlog::warn("Nothing found to build!"); | |||||
return; | |||||
} | } | ||||
return spec.output; | |||||
} | |||||
build_plan plan; | |||||
auto& pkg = plan.add_package(package_plan(man.name, man.namespace_)); | |||||
template <typename GetExeNameFn> | |||||
std::vector<fs::path> link_executables(source_kind sk, | |||||
GetExeNameFn&& get_exe_path, | |||||
const build_params& params, | |||||
const library& lib, | |||||
const object_file_index& obj_idx) { | |||||
return // | |||||
lib.all_sources() // | |||||
| filter([&](auto&& s) { return s.kind == sk; }) // | |||||
| transform([&](auto&& s) { | |||||
return link_one_exe(get_exe_path(s), s.path, params, lib, obj_idx); | |||||
}) // | |||||
| to_vector // | |||||
; | |||||
} | |||||
usage_requirement_map ureqs | |||||
= load_usage_requirements(params.root, params.out_root, params.lm_index); | |||||
struct link_results { | |||||
std::optional<fs::path> archive_path; | |||||
std::vector<fs::path> test_exes; | |||||
std::vector<fs::path> app_exes; | |||||
}; | |||||
link_results | |||||
link_project_lib(const build_params& params, const library& lib, const object_file_index& obj_idx) { | |||||
link_results res; | |||||
auto op_arc_path = create_lib_archive(params, lib, obj_idx); | |||||
if (op_arc_path) { | |||||
res.archive_path = *op_arc_path; | |||||
} | |||||
auto get_test_exe_path = [&](const source_file sf) { | |||||
return params.out_root | |||||
/ fs::relative(sf.path, params.root) | |||||
.replace_filename(sf.path.stem().stem().string() | |||||
+ params.toolchain.executable_suffix()); | |||||
}; | |||||
auto get_app_exe_path = [&](const source_file& sf) { | |||||
return params.out_root | |||||
/ (sf.path.stem().stem().string() + params.toolchain.executable_suffix()); | |||||
}; | |||||
// Link test executables | |||||
if (params.build_tests) { | |||||
extend(res.test_exes, | |||||
link_executables(source_kind::test, get_test_exe_path, params, lib, obj_idx)); | |||||
library_build_params lib_params; | |||||
lib_params.build_tests = params.build_tests; | |||||
lib_params.build_apps = params.build_apps; | |||||
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)); | |||||
} | } | ||||
if (params.build_apps) { | |||||
extend(res.app_exes, | |||||
link_executables(source_kind::app, get_app_exe_path, params, lib, obj_idx)); | |||||
} | |||||
return res; | |||||
} | |||||
std::vector<link_results> link_project(const build_params& params, | |||||
const project& pr, | |||||
const std::vector<compile_file_plan>& compilations) { | |||||
auto obj_index = // | |||||
ranges::views::all(compilations) // | |||||
| transform([&](const compile_file_plan& comp) -> std::pair<fs::path, fs::path> { | |||||
return std::pair(comp.source.path, | |||||
comp.get_object_file_path( | |||||
build_env{params.toolchain, params.out_root})); | |||||
}) // | |||||
| ranges::to<object_file_index>() // | |||||
; | |||||
auto libs = iter_libraries(pr); | |||||
return libs // | |||||
| transform([&](auto&& lib) { return link_project_lib(params, lib, obj_index); }) // | |||||
| to_vector; | |||||
} | |||||
} // namespace | |||||
void dds::build(const build_params& params, const package_manifest&) { | |||||
auto libs = collect_libraries(params.root); | |||||
// auto sroot = dds::sroot{params.root}; | |||||
// auto comp_rules = sroot.base_compile_rules(); | |||||
// sroot_build_params sr_params; | |||||
// sr_params.main_name = man.name; | |||||
// sr_params.build_tests = params.build_tests; | |||||
// sr_params.build_apps = params.build_apps; | |||||
// sr_params.compile_rules = comp_rules; | |||||
// build_plan plan; | |||||
// plan.add_sroot(sroot, sr_params); | |||||
// plan.compile_all(params.toolchain, params.parallel_jobs, params.out_root); | |||||
dds::build_env env{params.toolchain, params.out_root}; | |||||
plan.compile_all(env, params.parallel_jobs); | |||||
plan.archive_all(env, params.parallel_jobs); | |||||
plan.link_all(env, params.parallel_jobs); | |||||
auto project = project::from_directory(params.root); | auto project = project::from_directory(params.root); | ||||
auto compiles = collect_compiles(params, project); | |||||
dds::build_env env{params.toolchain, params.out_root}; | |||||
dds::execute_all(compiles, params.parallel_jobs, env); | |||||
// dds::execute_all(compiles, params.parallel_jobs, env); | |||||
using namespace ranges::views; | using namespace ranges::views; | ||||
auto link_res = link_project(params, project, compiles); | |||||
auto all_tests = link_res // | |||||
| transform([](auto&& link) { return link.test_exes; }) // | |||||
| ranges::actions::join; | |||||
int n_test_fails = 0; | |||||
for (path_ref test_exe : all_tests) { | |||||
spdlog::info("Running test: {}", fs::relative(test_exe, params.out_root).string()); | |||||
const auto test_res = run_proc({test_exe.string()}); | |||||
if (!test_res.okay()) { | |||||
spdlog::error("TEST FAILED\n{}", test_res.output); | |||||
n_test_fails++; | |||||
} | |||||
} | |||||
if (n_test_fails) { | |||||
throw compile_failure("Test failures during build"); | |||||
} | |||||
if (params.do_export) { | |||||
export_project(params, project); | |||||
} | |||||
// auto link_res = link_project(params, project, compiles); | |||||
// auto all_tests = link_res // | |||||
// | transform([](auto&& link) { return link.test_exes; }) // | |||||
// | ranges::actions::join; | |||||
// int n_test_fails = 0; | |||||
// for (path_ref test_exe : all_tests) { | |||||
// spdlog::info("Running test: {}", fs::relative(test_exe, params.out_root).string()); | |||||
// const auto test_res = run_proc({test_exe.string()}); | |||||
// if (!test_res.okay()) { | |||||
// spdlog::error("TEST FAILED\n{}", test_res.output); | |||||
// n_test_fails++; | |||||
// } | |||||
// } | |||||
// if (n_test_fails) { | |||||
// throw compile_failure("Test failures during build"); | |||||
// } | |||||
// if (params.do_export) { | |||||
// export_project(params, project); | |||||
// } | |||||
} | } |
#include "./compile.hpp" | |||||
#include <dds/proc.hpp> | |||||
#include <dds/util/algo.hpp> | |||||
#include <dds/util/signal.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
#include <atomic> | |||||
#include <mutex> | |||||
#include <thread> | |||||
#include <vector> | |||||
using namespace dds; | |||||
void compile_file_plan::compile(const build_env& env) const { | |||||
const auto obj_path = get_object_file_path(env); | |||||
fs::create_directories(obj_path.parent_path()); | |||||
spdlog::info("[{}] Compile: {}", | |||||
qualifier, | |||||
fs::relative(source.path, source.basis_path).string()); | |||||
auto start_time = std::chrono::steady_clock::now(); | |||||
compile_file_spec spec{source.path, obj_path}; | |||||
spec.enable_warnings = rules.enable_warnings(); | |||||
extend(spec.include_dirs, rules.include_dirs()); | |||||
extend(spec.definitions, rules.defs()); | |||||
auto cmd = env.toolchain.create_compile_command(spec); | |||||
auto compile_res = run_proc(cmd); | |||||
auto end_time = std::chrono::steady_clock::now(); | |||||
auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
spdlog::info("[{}] Compile: {} - {:n}ms", | |||||
qualifier, | |||||
fs::relative(source.path, source.basis_path).string(), | |||||
dur_ms.count()); | |||||
if (!compile_res.okay()) { | |||||
spdlog::error("Compilation failed: {}", source.path.string()); | |||||
spdlog::error("Subcommand FAILED: {}\n{}", quote_command(cmd), compile_res.output); | |||||
throw compile_failure(fmt::format("Compilation failed for {}", source.path.string())); | |||||
} | |||||
// MSVC prints the filename of the source file. Dunno why, but they do. | |||||
if (compile_res.output.find(spec.source_path.filename().string() + "\r\n") == 0) { | |||||
compile_res.output.erase(0, spec.source_path.filename().string().length() + 2); | |||||
} | |||||
if (!compile_res.output.empty()) { | |||||
spdlog::warn("While compiling file {} [{}]:\n{}", | |||||
spec.source_path.string(), | |||||
quote_command(cmd), | |||||
compile_res.output); | |||||
} | |||||
} | |||||
fs::path compile_file_plan::get_object_file_path(const build_env& env) const noexcept { | |||||
auto relpath = fs::relative(source.path, source.basis_path); | |||||
auto ret = env.output_root / subdir / relpath; | |||||
ret.replace_filename(relpath.filename().string() + env.toolchain.object_suffix()); | |||||
return ret; | |||||
} | |||||
void dds::execute_all(const std::vector<compile_file_plan>& compilations, | |||||
int n_jobs, | |||||
const build_env& env) { | |||||
// We don't bother with a nice thread pool, as the overhead of compiling | |||||
// source files dwarfs the cost of interlocking. | |||||
std::mutex mut; | |||||
auto comp_iter = compilations.begin(); | |||||
const auto end_iter = compilations.end(); | |||||
std::vector<std::exception_ptr> exceptions; | |||||
auto compile_one = [&]() mutable { | |||||
while (true) { | |||||
std::unique_lock lk{mut}; | |||||
if (!exceptions.empty()) { | |||||
break; | |||||
} | |||||
if (comp_iter == end_iter) { | |||||
break; | |||||
} | |||||
auto& compilation = *comp_iter++; | |||||
lk.unlock(); | |||||
try { | |||||
compilation.compile(env); | |||||
cancellation_point(); | |||||
} catch (...) { | |||||
lk.lock(); | |||||
exceptions.push_back(std::current_exception()); | |||||
break; | |||||
} | |||||
} | |||||
}; | |||||
std::unique_lock lk{mut}; | |||||
std::vector<std::thread> threads; | |||||
if (n_jobs < 1) { | |||||
n_jobs = std::thread::hardware_concurrency() + 2; | |||||
} | |||||
std::generate_n(std::back_inserter(threads), n_jobs, [&] { return std::thread(compile_one); }); | |||||
spdlog::info("Parallel compile with {} threads", threads.size()); | |||||
lk.unlock(); | |||||
for (auto& t : threads) { | |||||
t.join(); | |||||
} | |||||
for (auto eptr : exceptions) { | |||||
try { | |||||
std::rethrow_exception(eptr); | |||||
} catch (const std::exception& e) { | |||||
spdlog::error(e.what()); | |||||
} | |||||
} | |||||
if (!exceptions.empty()) { | |||||
throw compile_failure("Failed to compile library sources"); | |||||
} | |||||
} |
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <range/v3/action/join.hpp> | #include <range/v3/action/join.hpp> | ||||
#include <range/v3/view/concat.hpp> | |||||
#include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
#include <range/v3/view/join.hpp> | #include <range/v3/view/join.hpp> | ||||
#include <range/v3/view/repeat_n.hpp> | |||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
#include <range/v3/view/zip.hpp> | |||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
using namespace dds; | using namespace dds; | ||||
library_plan library_plan::create(const library& lib, const library_build_params& params) { | |||||
std::vector<compile_file_plan> compile_files; | |||||
std::vector<create_exe_plan> link_executables; | |||||
std::optional<create_archive_plan> create_archive; | |||||
bool should_create_archive = false; | |||||
std::vector<source_file> app_sources; | |||||
std::vector<source_file> test_sources; | |||||
std::vector<source_file> lib_sources; | |||||
auto src_dir = lib.src_dir(); | |||||
if (src_dir.exists()) { | |||||
auto all_sources = src_dir.sources(); | |||||
auto to_compile = all_sources | ranges::views::filter([&](const source_file& sf) { | |||||
return (sf.kind == source_kind::source | |||||
|| (sf.kind == source_kind::app && params.build_apps) | |||||
|| (sf.kind == source_kind::test && params.build_tests)); | |||||
}); | |||||
for (const auto& sfile : to_compile) { | |||||
compile_file_plan cf_plan; | |||||
cf_plan.source = sfile; | |||||
cf_plan.qualifier = lib.manifest().name; | |||||
cf_plan.rules = params.compile_rules; | |||||
cf_plan.subdir = fs::path("obj") / lib.manifest().name; | |||||
compile_files.push_back(std::move(cf_plan)); | |||||
if (sfile.kind == source_kind::test) { | |||||
test_sources.push_back(sfile); | |||||
} else if (sfile.kind == source_kind::app) { | |||||
app_sources.push_back(sfile); | |||||
} else { | |||||
should_create_archive = true; | |||||
lib_sources.push_back(sfile); | |||||
} | |||||
} | |||||
} | |||||
if (!app_sources.empty() || !test_sources.empty()) { | |||||
spdlog::critical("Apps/tests not implemented on this code path"); | |||||
} | |||||
if (should_create_archive) { | |||||
create_archive_plan ar_plan; | |||||
ar_plan.name = lib.manifest().name; | |||||
ar_plan.out_dir = params.out_subdir; | |||||
create_archive.emplace(std::move(ar_plan)); | |||||
} | |||||
return library_plan{lib.manifest().name, | |||||
lib.path(), | |||||
params.out_subdir, | |||||
compile_files, | |||||
create_archive, | |||||
link_executables}; | |||||
} | |||||
namespace { | namespace { | ||||
template <typename Range, typename Fn> | template <typename Range, typename Fn> | ||||
} // namespace | } // namespace | ||||
fs::path create_archive_plan::archive_file_path(const build_env& env) const noexcept { | |||||
return env.output_root / fmt::format("{}{}{}", "lib", name, env.toolchain.archive_suffix()); | |||||
} | |||||
void create_archive_plan::archive(const build_env& env, | |||||
const std::vector<fs::path>& objects) const { | |||||
archive_spec ar; | |||||
ar.input_files = objects; | |||||
ar.out_path = archive_file_path(env); | |||||
auto ar_cmd = env.toolchain.create_archive_command(ar); | |||||
auto out_relpath = fs::relative(ar.out_path, env.output_root).string(); | |||||
spdlog::info("[{}] Archive: {}", name, out_relpath); | |||||
auto start_time = std::chrono::steady_clock::now(); | |||||
auto ar_res = run_proc(ar_cmd); | |||||
auto end_time = std::chrono::steady_clock::now(); | |||||
auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
spdlog::info("[{}] Archive: {} - {:n}ms", name, out_relpath, dur_ms.count()); | |||||
if (!ar_res.okay()) { | |||||
spdlog::error("Creating static library archive failed: {}", out_relpath); | |||||
spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | |||||
throw std::runtime_error( | |||||
fmt::format("Creating archive [{}] failed for '{}'", out_relpath, name)); | |||||
} | |||||
} | |||||
namespace { | namespace { | ||||
auto all_libraries(const build_plan& plan) { | auto all_libraries(const build_plan& plan) { | ||||
return // | |||||
plan.build_packages // | |||||
| ranges::views::transform(&package_plan::create_libraries) // | |||||
| ranges::views::join // | |||||
return // | |||||
plan.packages() // | |||||
| ranges::views::transform(&package_plan::libraries) // | |||||
| ranges::views::join // | |||||
; | ; | ||||
} | } | ||||
} // namespace | } // namespace | ||||
void build_plan::compile_all(const build_env& env, int njobs) const { | void build_plan::compile_all(const build_env& env, int njobs) const { | ||||
auto all_compiles = // | |||||
all_libraries(*this) // | |||||
| ranges::views::transform(&library_plan::compile_files) // | |||||
| ranges::views::join // | |||||
auto lib_compiles = // | |||||
all_libraries(*this) // | |||||
| ranges::views::transform(&library_plan::create_archive) // | |||||
| ranges::views::filter([&](auto&& opt) { return bool(opt); }) // | |||||
| ranges::views::transform([&](auto&& opt) -> auto& { return opt->compile_files(); }) // | |||||
| ranges::views::join // | |||||
; | |||||
auto exe_compiles = // | |||||
all_libraries(*this) // | |||||
| ranges::views::transform(&library_plan::executables) // | |||||
| ranges::views::join // | |||||
| ranges::views::transform(&link_executable_plan::main_compile_file) // | |||||
; | ; | ||||
auto okay = parallel_run(all_compiles, njobs, [&](const auto& cf) { cf.compile(env); }); | |||||
auto all_compiles = ranges::views::concat(lib_compiles, exe_compiles); | |||||
auto okay | |||||
= parallel_run(all_compiles, njobs, [&](const compile_file_plan& cf) { cf.compile(env); }); | |||||
if (!okay) { | if (!okay) { | ||||
throw std::runtime_error("Compilation failed."); | throw std::runtime_error("Compilation failed."); | ||||
} | } | ||||
void build_plan::archive_all(const build_env& env, int njobs) const { | void build_plan::archive_all(const build_env& env, int njobs) const { | ||||
parallel_run(all_libraries(*this), njobs, [&](const library_plan& lib) { | parallel_run(all_libraries(*this), njobs, [&](const library_plan& lib) { | ||||
if (!lib.create_archive) { | |||||
return; | |||||
if (lib.create_archive()) { | |||||
lib.create_archive()->archive(env); | |||||
} | } | ||||
const auto& objects = ranges::views::all(lib.compile_files) // | |||||
| ranges::views::filter([](auto&& comp) { | |||||
return comp.source.kind == source_kind::source; | |||||
}) // | |||||
| ranges::views::transform( | |||||
[&](auto&& comp) { return comp.get_object_file_path(env); }) // | |||||
| ranges::to_vector // | |||||
; | |||||
lib.create_archive->archive(env, objects); | |||||
}); | }); | ||||
} | } | ||||
void build_plan::link_all(const build_env& env, int) const { | |||||
for (auto&& lib : all_libraries(*this)) { | |||||
for (auto&& exe : lib.executables()) { | |||||
exe.link(env, lib); | |||||
} | |||||
} | |||||
} |
#pragma once | #pragma once | ||||
#include <dds/build/compile.hpp> | |||||
#include <dds/build/params.hpp> | #include <dds/build/params.hpp> | ||||
#include <dds/build/plan/archive.hpp> | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/build/plan/exe.hpp> | |||||
#include <dds/build/plan/library.hpp> | |||||
#include <dds/build/plan/package.hpp> | |||||
#include <dds/library.hpp> | #include <dds/library.hpp> | ||||
#include <dds/toolchain.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
namespace dds { | namespace dds { | ||||
struct create_archive_plan { | |||||
std::string name; | |||||
fs::path out_dir; | |||||
fs::path archive_file_path(const build_env& env) const noexcept; | |||||
void archive(const build_env& env, const std::vector<fs::path>& objects) const; | |||||
}; | |||||
struct create_exe_plan { | |||||
std::vector<fs::path> in_sources; | |||||
std::string name; | |||||
fs::path out_dir; | |||||
}; | |||||
struct library_plan { | |||||
std::string name; | |||||
fs::path source_root; | |||||
fs::path out_subdir; | |||||
std::vector<compile_file_plan> compile_files; | |||||
std::optional<create_archive_plan> create_archive; | |||||
std::vector<create_exe_plan> link_executables; | |||||
class build_plan { | |||||
std::vector<package_plan> _packages; | |||||
static library_plan create(const library& lib, const library_build_params& params); | |||||
}; | |||||
struct package_plan { | |||||
std::string name; | |||||
std::string namespace_; | |||||
std::vector<std::string> pkg_requires; | |||||
std::vector<library_plan> create_libraries; | |||||
void add_library(const library& lib, const library_build_params& params) { | |||||
create_libraries.push_back(library_plan::create(lib, params)); | |||||
public: | |||||
package_plan& add_package(package_plan p) noexcept { | |||||
return _packages.emplace_back(std::move(p)); | |||||
} | } | ||||
}; | |||||
struct build_plan { | |||||
std::vector<package_plan> build_packages; | |||||
void compile_all(const build_env& env, int njobs) const; | |||||
void archive_all(const build_env& env, int njobs) const; | |||||
auto& packages() const noexcept { return _packages; } | |||||
void compile_all(const build_env& env, int njobs) const; | |||||
void archive_all(const build_env& env, int njobs) const; | |||||
void link_all(const build_env& env, int njobs) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
#include "./archive.hpp" | |||||
#include <dds/proc.hpp> | |||||
#include <range/v3/view/transform.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
using namespace dds; | |||||
fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept { | |||||
return env.output_root / fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix()); | |||||
} | |||||
void create_archive_plan::archive(const build_env& env) const { | |||||
const auto objects = // | |||||
_compile_files // | |||||
| ranges::views::transform([&](auto&& cf) { return cf.calc_object_file_path(env); }) | |||||
| ranges::to_vector // | |||||
; | |||||
archive_spec ar; | |||||
ar.input_files = std::move(objects); | |||||
ar.out_path = calc_archive_file_path(env); | |||||
auto ar_cmd = env.toolchain.create_archive_command(ar); | |||||
auto out_relpath = fs::relative(ar.out_path, env.output_root).string(); | |||||
if (fs::exists(ar.out_path)) { | |||||
fs::remove(ar.out_path); | |||||
} | |||||
spdlog::info("[{}] Archive: {}", _name, out_relpath); | |||||
auto start_time = std::chrono::steady_clock::now(); | |||||
auto ar_res = run_proc(ar_cmd); | |||||
auto end_time = std::chrono::steady_clock::now(); | |||||
auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
spdlog::info("[{}] Archive: {} - {:n}ms", _name, out_relpath, dur_ms.count()); | |||||
if (!ar_res.okay()) { | |||||
spdlog::error("Creating static library archive failed: {}", out_relpath); | |||||
spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | |||||
throw std::runtime_error( | |||||
fmt::format("Creating archive [{}] failed for '{}'", out_relpath, _name)); | |||||
} | |||||
} |
#pragma once | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <string> | |||||
namespace dds { | |||||
class create_archive_plan { | |||||
std::string _name; | |||||
fs::path _subdir; | |||||
std::vector<compile_file_plan> _compile_files; | |||||
public: | |||||
create_archive_plan(std::string_view name, path_ref subdir, std::vector<compile_file_plan> cfs) | |||||
: _name(name) | |||||
, _subdir(subdir) | |||||
, _compile_files(std::move(cfs)) {} | |||||
fs::path calc_archive_file_path(build_env_ref env) const noexcept; | |||||
auto& compile_files() const noexcept { return _compile_files; } | |||||
void archive(build_env_ref env) const; | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <dds/toolchain.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
namespace dds { | |||||
struct build_env { | |||||
dds::toolchain toolchain; | |||||
fs::path output_root; | |||||
}; | |||||
using build_env_ref = const build_env&; | |||||
} // namespace dds |
#include "./compile_file.hpp" | |||||
#include <dds/proc.hpp> | |||||
#include <dds/util/algo.hpp> | |||||
#include <dds/util/signal.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
#include <atomic> | |||||
#include <mutex> | |||||
#include <thread> | |||||
#include <vector> | |||||
using namespace dds; | |||||
void compile_file_plan::compile(const build_env& env) const { | |||||
const auto obj_path = calc_object_file_path(env); | |||||
fs::create_directories(obj_path.parent_path()); | |||||
spdlog::info("[{}] Compile: {}", | |||||
_qualifier, | |||||
fs::relative(_source.path, _source.basis_path).string()); | |||||
auto start_time = std::chrono::steady_clock::now(); | |||||
compile_file_spec spec{_source.path, obj_path}; | |||||
spec.enable_warnings = _rules.enable_warnings(); | |||||
extend(spec.include_dirs, _rules.include_dirs()); | |||||
extend(spec.definitions, _rules.defs()); | |||||
auto cmd = env.toolchain.create_compile_command(spec); | |||||
auto compile_res = run_proc(cmd); | |||||
auto end_time = std::chrono::steady_clock::now(); | |||||
auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
spdlog::info("[{}] Compile: {} - {:n}ms", | |||||
_qualifier, | |||||
fs::relative(_source.path, _source.basis_path).string(), | |||||
dur_ms.count()); | |||||
if (!compile_res.okay()) { | |||||
spdlog::error("Compilation failed: {}", _source.path.string()); | |||||
spdlog::error("Subcommand FAILED: {}\n{}", quote_command(cmd), compile_res.output); | |||||
throw compile_failure(fmt::format("Compilation failed for {}", _source.path.string())); | |||||
} | |||||
// MSVC prints the filename of the source file. Dunno why, but they do. | |||||
if (compile_res.output.find(spec.source_path.filename().string() + "\r\n") == 0) { | |||||
compile_res.output.erase(0, spec.source_path.filename().string().length() + 2); | |||||
} | |||||
if (!compile_res.output.empty()) { | |||||
spdlog::warn("While compiling file {} [{}]:\n{}", | |||||
spec.source_path.string(), | |||||
quote_command(cmd), | |||||
compile_res.output); | |||||
} | |||||
} | |||||
fs::path compile_file_plan::calc_object_file_path(const build_env& env) const noexcept { | |||||
auto relpath = fs::relative(_source.path, _source.basis_path); | |||||
auto ret = env.output_root / _subdir / relpath; | |||||
ret.replace_filename(relpath.filename().string() + env.toolchain.object_suffix()); | |||||
return ret; | |||||
} |
#pragma once | #pragma once | ||||
#include <dds/build/plan/base.hpp> | |||||
#include <dds/source.hpp> | #include <dds/source.hpp> | ||||
#include <dds/toolchain.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <memory> | #include <memory> | ||||
#include <optional> | |||||
#include <stdexcept> | |||||
namespace dds { | namespace dds { | ||||
struct build_env { | |||||
dds::toolchain toolchain; | |||||
fs::path output_root; | |||||
}; | |||||
struct compile_failure : std::runtime_error { | struct compile_failure : std::runtime_error { | ||||
using runtime_error::runtime_error; | using runtime_error::runtime_error; | ||||
}; | }; | ||||
auto& enable_warnings() const noexcept { return _impl->enable_warnings; } | auto& enable_warnings() const noexcept { return _impl->enable_warnings; } | ||||
}; | }; | ||||
struct compile_file_plan { | |||||
shared_compile_file_rules rules; | |||||
fs::path subdir; | |||||
dds::source_file source; | |||||
std::string qualifier; | |||||
class compile_file_plan { | |||||
shared_compile_file_rules _rules; | |||||
source_file _source; | |||||
std::string _qualifier; | |||||
fs::path _subdir; | |||||
fs::path get_object_file_path(const build_env& env) const noexcept; | |||||
void compile(const build_env&) const; | |||||
public: | |||||
compile_file_plan(shared_compile_file_rules rules, | |||||
source_file sf, | |||||
std::string_view qual, | |||||
path_ref subdir) | |||||
: _rules(rules) | |||||
, _source(std::move(sf)) | |||||
, _qualifier(qual) | |||||
, _subdir(subdir) {} | |||||
const source_file& source() const noexcept { return _source; } | |||||
path_ref source_path() const noexcept { return _source.path; } | |||||
fs::path calc_object_file_path(build_env_ref env) const noexcept; | |||||
void compile(build_env_ref) const; | |||||
}; | }; | ||||
void execute_all(const std::vector<compile_file_plan>&, int n_jobs, const build_env& env); | |||||
} // namespace dds | |||||
} // namespace dds |
#include "./exe.hpp" | |||||
#include <dds/build/plan/library.hpp> | |||||
#include <dds/proc.hpp> | |||||
#include <range/v3/algorithm/find_if.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
#include <cassert> | |||||
using namespace dds; | |||||
fs::path link_executable_plan::calc_executable_path(build_env_ref env) const noexcept { | |||||
return env.output_root / _out_subdir / (_name + env.toolchain.executable_suffix()); | |||||
} | |||||
void link_executable_plan::link(build_env_ref env, const library_plan& lib) const { | |||||
const auto out_path = calc_executable_path(env); | |||||
link_exe_spec spec; | |||||
spec.output = out_path; | |||||
spec.inputs = _input_libs; | |||||
if (lib.create_archive()) { | |||||
spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env)); | |||||
auto main_obj = _main_compile.calc_object_file_path(env); | |||||
spec.inputs.push_back(std::move(main_obj)); | |||||
} | |||||
const auto link_command = env.toolchain.create_link_executable_command(spec); | |||||
spdlog::info("Linking executable: {}", fs::relative(spec.output, env.output_root).string()); | |||||
fs::create_directories(out_path.parent_path()); | |||||
auto proc_res = run_proc(link_command); | |||||
if (!proc_res.okay()) { | |||||
throw compile_failure( | |||||
fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | |||||
spec.output.string(), | |||||
quote_command(link_command), | |||||
proc_res.retc, | |||||
proc_res.output)); | |||||
} | |||||
} |
#pragma once | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <set> | |||||
#include <tuple> | |||||
#include <vector> | |||||
namespace dds { | |||||
class library_plan; | |||||
class link_executable_plan { | |||||
std::vector<fs::path> _input_libs; | |||||
compile_file_plan _main_compile; | |||||
fs::path _out_subdir; | |||||
std::string _name; | |||||
public: | |||||
link_executable_plan(std::vector<fs::path> in_libs, | |||||
compile_file_plan cfp, | |||||
path_ref out_subdir, | |||||
std::string name_) | |||||
: _input_libs(std::move(in_libs)) | |||||
, _main_compile(std::move(cfp)) | |||||
, _out_subdir(out_subdir) | |||||
, _name(std::move(name_)) {} | |||||
auto& main_compile_file() const noexcept { return _main_compile; } | |||||
fs::path calc_executable_path(const build_env& env) const noexcept; | |||||
void link(const build_env&, const library_plan&) const; | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <build/plan/package.hpp> | |||||
#include <map> | |||||
namespace dds { | |||||
class build_plan2 {}; | |||||
} // namespace dds |
#include "./library.hpp" | |||||
#include <dds/util/algo.hpp> | |||||
#include <range/v3/view/filter.hpp> | |||||
#include <range/v3/view/concat.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
using namespace dds; | |||||
library_plan library_plan::create(const library& lib, | |||||
const library_build_params& params, | |||||
const usage_requirement_map& ureqs) { | |||||
std::vector<compile_file_plan> compile_files; | |||||
std::vector<link_executable_plan> link_executables; | |||||
std::optional<create_archive_plan> create_archive; | |||||
std::vector<source_file> app_sources; | |||||
std::vector<source_file> test_sources; | |||||
std::vector<source_file> lib_sources; | |||||
auto src_dir = lib.src_dir(); | |||||
if (src_dir.exists()) { | |||||
auto all_sources = src_dir.sources(); | |||||
auto to_compile = all_sources | ranges::views::filter([&](const source_file& sf) { | |||||
return (sf.kind == source_kind::source | |||||
|| (sf.kind == source_kind::app && params.build_apps) | |||||
|| (sf.kind == source_kind::test && params.build_tests)); | |||||
}); | |||||
for (const auto& sfile : to_compile) { | |||||
if (sfile.kind == source_kind::test) { | |||||
test_sources.push_back(sfile); | |||||
} else if (sfile.kind == source_kind::app) { | |||||
app_sources.push_back(sfile); | |||||
} else { | |||||
lib_sources.push_back(sfile); | |||||
} | |||||
} | |||||
} | |||||
auto compile_rules = lib.base_compile_rules(); | |||||
for (const auto& use : lib.manifest().uses) { | |||||
ureqs.apply(compile_rules, use.namespace_, use.name); | |||||
} | |||||
for (const auto& sf : lib_sources) { | |||||
compile_files.emplace_back(compile_rules, | |||||
sf, | |||||
lib.manifest().name, | |||||
params.out_subdir / "obj"); | |||||
} | |||||
if (!lib_sources.empty()) { | |||||
create_archive.emplace(lib.manifest().name, params.out_subdir, std::move(compile_files)); | |||||
} | |||||
std::vector<fs::path> in_libs; | |||||
for (auto& use : lib.manifest().uses) { | |||||
extend(in_libs, ureqs.link_paths(use.namespace_, use.name)); | |||||
} | |||||
for (auto& link : lib.manifest().links) { | |||||
extend(in_libs, ureqs.link_paths(link.namespace_, link.name)); | |||||
} | |||||
for (const source_file& source : ranges::views::concat(app_sources, test_sources)) { | |||||
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, | |||||
source, | |||||
lib.manifest().name, | |||||
params.out_subdir / "obj"), | |||||
subdir, | |||||
source.path.stem().stem().string()); | |||||
} | |||||
if (!app_sources.empty()) { | |||||
spdlog::critical("Apps not implemented on this code path!"); | |||||
} | |||||
return library_plan{lib.manifest().name, | |||||
lib.path(), | |||||
std::move(create_archive), | |||||
std::move(link_executables)}; | |||||
} |
#pragma once | |||||
#include <dds/build/plan/archive.hpp> | |||||
#include <dds/build/plan/exe.hpp> | |||||
#include <dds/library.hpp> | |||||
#include <dds/usage_reqs.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <map> | |||||
#include <optional> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace dds { | |||||
class library_plan { | |||||
std::string _name; | |||||
fs::path _source_root; | |||||
std::optional<create_archive_plan> _create_archive; | |||||
std::vector<link_executable_plan> _link_exes; | |||||
public: | |||||
library_plan(std::string_view name, | |||||
path_ref source_root, | |||||
std::optional<create_archive_plan> ar, | |||||
std::vector<link_executable_plan> exes) | |||||
: _name(name) | |||||
, _source_root(source_root) | |||||
, _create_archive(std::move(ar)) | |||||
, _link_exes(std::move(exes)) {} | |||||
path_ref source_root() const noexcept { return _source_root; } | |||||
auto& name() const noexcept { return _name; } | |||||
auto& create_archive() const noexcept { return _create_archive; } | |||||
auto& executables() const noexcept { return _link_exes; } | |||||
static library_plan | |||||
create(const library&, const library_build_params&, const usage_requirement_map&); | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <dds/build/plan/library.hpp> | |||||
#include <map> | |||||
#include <string> | |||||
namespace dds { | |||||
class package_plan { | |||||
std::string _name; | |||||
std::string _namespace; | |||||
std::vector<library_plan> _libraries; | |||||
public: | |||||
package_plan(std::string_view name, std::string_view namespace_) | |||||
: _name(name) | |||||
, _namespace(namespace_) {} | |||||
void add_library(library_plan lp) { _libraries.emplace_back(std::move(lp)); } | |||||
auto& name() const noexcept { return _name; } | |||||
auto& namespace_() const noexcept { return _namespace; } | |||||
auto& libraries() const noexcept { return _libraries; } | |||||
}; | |||||
} // namespace dds |
man.dependencies.end()); | man.dependencies.end()); | ||||
}); | }); | ||||
auto plan = dds::create_deps_build_plan(deps); | |||||
auto tc = tc_filepath.get_toolchain(); | auto tc = tc_filepath.get_toolchain(); | ||||
auto bdir = build_dir.Get(); | auto bdir = build_dir.Get(); | ||||
dds::build_env env{std::move(tc), bdir}; | dds::build_env env{std::move(tc), bdir}; | ||||
auto plan = dds::create_deps_build_plan(deps, env); | |||||
plan.compile_all(env, 6); | plan.compile_all(env, 6); | ||||
plan.archive_all(env, 6); | plan.archive_all(env, 6); | ||||
if (!no_lmi.Get()) { | if (!no_lmi.Get()) { |
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
#include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
#include <dds/usage_reqs.hpp> | |||||
#include <dds/util/algo.hpp> | |||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <libman/index.hpp> | #include <libman/index.hpp> | ||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
} | } | ||||
using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | ||||
using sdist_names = std::set<std::string>; | |||||
namespace { | namespace { | ||||
void linkup_dependencies(shared_compile_file_rules& rules, | |||||
const package_manifest& man, | |||||
const sdist_index_type& sd_idx) { | |||||
void resolve_ureqs_(shared_compile_file_rules& rules, | |||||
const package_manifest& man, | |||||
const sdist_index_type& sd_idx) { | |||||
for (const dependency& dep : man.dependencies) { | for (const dependency& dep : man.dependencies) { | ||||
auto found = sd_idx.find(dep.name); | auto found = sd_idx.find(dep.name); | ||||
if (found == sd_idx.end()) { | if (found == sd_idx.end()) { | ||||
dep.name, | dep.name, | ||||
man.name)); | man.name)); | ||||
} | } | ||||
linkup_dependencies(rules, found->second.get().manifest, sd_idx); | |||||
resolve_ureqs_(rules, found->second.get().manifest, sd_idx); | |||||
auto lib_src = found->second.get().path / "src"; | auto lib_src = found->second.get().path / "src"; | ||||
auto lib_include = found->second.get().path / "include"; | auto lib_include = found->second.get().path / "include"; | ||||
if (fs::exists(lib_include)) { | if (fs::exists(lib_include)) { | ||||
} | } | ||||
} | } | ||||
void add_sdist_to_dep_plan(build_plan& plan, const sdist& sd, const sdist_index_type& sd_idx) { | |||||
auto& pkg = plan.build_packages.emplace_back(); | |||||
pkg.name = sd.manifest.name; | |||||
pkg.namespace_ = sd.manifest.namespace_; | |||||
auto libs = collect_libraries(sd.path); | |||||
void resolve_ureqs(shared_compile_file_rules rules, | |||||
const sdist& sd, | |||||
const library& lib, | |||||
const library_plan& lib_plan, | |||||
build_env_ref env, | |||||
usage_requirement_map& ureqs) { | |||||
// Add the transitive requirements for this library to our compile rules. | |||||
for (auto&& use : lib.manifest().uses) { | |||||
ureqs.apply(rules, use.namespace_, use.name); | |||||
} | |||||
// Create usage requirements for this libary. | |||||
lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name); | |||||
reqs.include_paths.push_back(lib.public_include_dir()); | |||||
reqs.name = lib.manifest().name; | |||||
reqs.uses = lib.manifest().uses; | |||||
reqs.links = lib.manifest().links; | |||||
if (lib_plan.create_archive()) { | |||||
reqs.linkable_path = lib_plan.create_archive()->calc_archive_file_path(env); | |||||
} | |||||
// TODO: preprocessor definitions | |||||
} | |||||
void add_sdist_to_dep_plan(build_plan& plan, | |||||
const sdist& sd, | |||||
build_env_ref env, | |||||
const sdist_index_type& sd_idx, | |||||
usage_requirement_map& ureqs, | |||||
sdist_names& already_added) { | |||||
if (already_added.find(sd.manifest.name) != already_added.end()) { | |||||
// We've already loaded this package into the plan. | |||||
return; | |||||
} | |||||
spdlog::debug("Add to plan: {}", sd.manifest.name); | |||||
// First, load every dependency | |||||
for (const auto& dep : sd.manifest.dependencies) { | |||||
auto other = sd_idx.find(dep.name); | |||||
assert(other != sd_idx.end() | |||||
&& "Failed to load a transitive dependency shortly after initializing them. What?"); | |||||
add_sdist_to_dep_plan(plan, other->second, env, sd_idx, ureqs, already_added); | |||||
} | |||||
// Record that we have been processed: | |||||
already_added.insert(sd.manifest.name); | |||||
// Add the package: | |||||
auto& pkg = plan.add_package(package_plan(sd.manifest.name, sd.manifest.namespace_)); | |||||
auto libs = collect_libraries(sd.path); | |||||
for (const auto& lib : libs) { | for (const auto& lib : libs) { | ||||
shared_compile_file_rules comp_rules = lib.base_compile_rules(); | shared_compile_file_rules comp_rules = lib.base_compile_rules(); | ||||
linkup_dependencies(comp_rules, sd.manifest, sd_idx); | |||||
library_build_params params; | |||||
params.compile_rules = comp_rules; | |||||
pkg.add_library(lib, params); | |||||
library_build_params params; | |||||
auto lib_plan = library_plan::create(lib, params, ureqs); | |||||
resolve_ureqs(comp_rules, sd, lib, lib_plan, env, ureqs); | |||||
pkg.add_library(std::move(lib_plan)); | |||||
} | } | ||||
} | } | ||||
} // namespace | } // namespace | ||||
build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps) { | |||||
auto sd_idx = deps | ranges::views::transform([](const auto& sd) { | |||||
return std::pair(sd.manifest.name, std::cref(sd)); | |||||
}) | |||||
build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env) { | |||||
auto sd_idx = deps // | |||||
| ranges::views::transform( | |||||
[](const auto& sd) { return std::pair(sd.manifest.name, std::cref(sd)); }) // | |||||
| ranges::to<sdist_index_type>(); | | ranges::to<sdist_index_type>(); | ||||
build_plan plan; | |||||
build_plan plan; | |||||
usage_requirement_map ureqs; | |||||
sdist_names already_added; | |||||
for (const sdist& sd : deps) { | for (const sdist& sd : deps) { | ||||
spdlog::info("Recording dependency: {}", sd.manifest.name); | spdlog::info("Recording dependency: {}", sd.manifest.name); | ||||
add_sdist_to_dep_plan(plan, sd, sd_idx); | |||||
add_sdist_to_dep_plan(plan, sd, env, sd_idx, ureqs, already_added); | |||||
} | } | ||||
return plan; | return plan; | ||||
} | } | ||||
namespace { | namespace { | ||||
fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_env& env) { | fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_env& env) { | ||||
auto fname = lib.name + ".lml"; | |||||
auto fname = lib.name() + ".lml"; | |||||
auto lml_path = libdir / fname; | auto lml_path = libdir / fname; | ||||
std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
kvs.emplace_back("Type", "Library"); | kvs.emplace_back("Type", "Library"); | ||||
kvs.emplace_back("Name", lib.name); | |||||
if (lib.create_archive) { | |||||
kvs.emplace_back("Name", lib.name()); | |||||
if (lib.create_archive()) { | |||||
kvs.emplace_back("Path", | kvs.emplace_back("Path", | ||||
fs::relative(lib.create_archive->archive_file_path(env), | |||||
fs::relative(lib.create_archive()->calc_archive_file_path(env), | |||||
lml_path.parent_path()) | lml_path.parent_path()) | ||||
.string()); | .string()); | ||||
} | } | ||||
auto pub_inc_dir = lib.source_root / "include"; | |||||
auto src_dir = lib.source_root / "src"; | |||||
auto pub_inc_dir = lib.source_root() / "include"; | |||||
auto src_dir = lib.source_root() / "src"; | |||||
if (fs::exists(src_dir)) { | if (fs::exists(src_dir)) { | ||||
pub_inc_dir = src_dir; | pub_inc_dir = src_dir; | ||||
} | } | ||||
} | } | ||||
fs::path generate_lmp(const package_plan& pkg, path_ref basedir, const build_env& env) { | fs::path generate_lmp(const package_plan& pkg, path_ref basedir, const build_env& env) { | ||||
auto fname = pkg.name + ".lmp"; | |||||
auto fname = pkg.name() + ".lmp"; | |||||
auto lmp_path = basedir / fname; | auto lmp_path = basedir / fname; | ||||
std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
kvs.emplace_back("Type", "Package"); | kvs.emplace_back("Type", "Package"); | ||||
kvs.emplace_back("Name", pkg.name); | |||||
kvs.emplace_back("Namespace", pkg.namespace_); | |||||
kvs.emplace_back("Name", pkg.name()); | |||||
kvs.emplace_back("Namespace", pkg.namespace_()); | |||||
for (auto&& lib : pkg.create_libraries) { | |||||
auto lml = generate_lml(lib, basedir / pkg.name, env); | |||||
for (auto&& lib : pkg.libraries()) { | |||||
auto lml = generate_lml(lib, basedir / pkg.name(), env); | |||||
kvs.emplace_back("Library", fs::relative(lml, lmp_path.parent_path()).string()); | kvs.emplace_back("Library", fs::relative(lml, lmp_path.parent_path()).string()); | ||||
} | } | ||||
auto lm_items_dir = out_filepath.parent_path() / "_libman"; | auto lm_items_dir = out_filepath.parent_path() / "_libman"; | ||||
std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
kvs.emplace_back("Type", "Index"); | kvs.emplace_back("Type", "Index"); | ||||
for (const package_plan& pkg : plan.build_packages) { | |||||
for (const package_plan& pkg : plan.packages()) { | |||||
auto pkg_lmp = generate_lmp(pkg, lm_items_dir, env); | auto pkg_lmp = generate_lmp(pkg, lm_items_dir, env); | ||||
kvs.emplace_back("Package", | kvs.emplace_back("Package", | ||||
fmt::format("{}; {}", | fmt::format("{}; {}", | ||||
pkg.name, | |||||
pkg.name(), | |||||
fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | ||||
} | } | ||||
lm::write_pairs(out_filepath, kvs); | lm::write_pairs(out_filepath, kvs); |
return acc; | return acc; | ||||
} | } | ||||
build_plan create_deps_build_plan(const std::vector<sdist>& deps); | |||||
build_plan create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env); | |||||
void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | ||||
#include <dds/library.hpp> | #include <dds/library.hpp> | ||||
#include <dds/build/compile.hpp> | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/build/source_dir.hpp> | #include <dds/build/source_dir.hpp> | ||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#pragma once | #pragma once | ||||
#include <dds/build/compile.hpp> | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/build/source_dir.hpp> | #include <dds/build/source_dir.hpp> | ||||
#include <dds/library_manifest.hpp> | #include <dds/library_manifest.hpp> | ||||
#include <dds/source.hpp> | #include <dds/source.hpp> | ||||
}; | }; | ||||
struct library_build_params { | struct library_build_params { | ||||
fs::path out_subdir; | |||||
bool build_tests = false; | |||||
bool build_apps = false; | |||||
std::vector<fs::path> rt_link_libraries; | |||||
shared_compile_file_rules compile_rules; | |||||
fs::path out_subdir; | |||||
bool build_tests = false; | |||||
bool build_apps = false; | |||||
}; | }; | ||||
std::vector<library> collect_libraries(path_ref where); | std::vector<library> collect_libraries(path_ref where); |
#include "./library_manifest.hpp" | #include "./library_manifest.hpp" | ||||
#include <dds/util/algo.hpp> | |||||
#include <range/v3/view/transform.hpp> | |||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
auto kvs = lm::parse_file(fpath); | auto kvs = lm::parse_file(fpath); | ||||
library_manifest ret; | library_manifest ret; | ||||
ret.name = fpath.parent_path().filename().string(); | ret.name = fpath.parent_path().filename().string(); | ||||
std::vector<std::string> uses_strings; | |||||
std::vector<std::string> links_strings; | |||||
lm::read(fmt::format("Reading library manifest {}", fpath.string()), | lm::read(fmt::format("Reading library manifest {}", fpath.string()), | ||||
kvs, | kvs, | ||||
lm::read_accumulate("Private-Include", ret.private_includes), | |||||
lm::read_accumulate("Private-Define", ret.private_defines), | |||||
lm::read_accumulate("Uses", ret.uses), | |||||
lm::read_accumulate("Links", ret.links), | |||||
lm::read_opt("Name", ret.name), | |||||
lm::read_accumulate("Uses", uses_strings), | |||||
lm::read_accumulate("Links", links_strings), | |||||
lm::read_required("Name", ret.name), | |||||
lm::reject_unknown()); | lm::reject_unknown()); | ||||
extend(ret.uses, ranges::views::transform(uses_strings, lm::split_usage_string)); | |||||
extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | |||||
return ret; | return ret; | ||||
} | } |
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <libman/library.hpp> | |||||
#include <vector> | #include <vector> | ||||
namespace dds { | namespace dds { | ||||
struct library_manifest { | struct library_manifest { | ||||
std::string name; | |||||
std::vector<fs::path> private_includes; | |||||
std::vector<std::string> private_defines; | |||||
std::vector<std::string> uses; | |||||
std::vector<std::string> links; | |||||
std::string name; | |||||
std::vector<lm::usage> uses; | |||||
std::vector<lm::usage> links; | |||||
static library_manifest load_from_file(const fs::path&); | static library_manifest load_from_file(const fs::path&); | ||||
}; | }; |
#include "./usage_reqs.hpp" | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/util/algo.hpp> | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <stdexcept> | |||||
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}); | |||||
if (found == _reqs.end()) { | |||||
return nullptr; | |||||
} | |||||
return &found->second; | |||||
} | |||||
lm::library& usage_requirement_map::add(std::string ns, std::string name) { | |||||
auto pair = std::pair(library_key{ns, name}, lm::library{}); | |||||
auto [inserted, did_insert] = _reqs.try_emplace(library_key{ns, name}, lm::library()); | |||||
if (!did_insert) { | |||||
throw std::runtime_error( | |||||
fmt::format("More than one library is registered as {}/{}", ns, name)); | |||||
} | |||||
return inserted->second; | |||||
} | |||||
void usage_requirement_map::apply(shared_compile_file_rules rules, | |||||
std::string ns, | |||||
std::string name) const { | |||||
auto reqs = get(ns, name); | |||||
if (!reqs) { | |||||
throw std::runtime_error( | |||||
fmt::format("Unable to resolve usage requirements for '{}/{}'", ns, name)); | |||||
} | |||||
for (auto&& use : reqs->uses) { | |||||
apply(rules, use.namespace_, use.name); | |||||
} | |||||
extend(rules.include_dirs(), reqs->include_paths); | |||||
extend(rules.defs(), reqs->preproc_defs); | |||||
} | |||||
usage_requirement_map usage_requirement_map::from_lm_index(const lm::index& idx) noexcept { | |||||
usage_requirement_map ret; | |||||
for (const auto& pkg : idx.packages) { | |||||
for (const auto& lib : pkg.libraries) { | |||||
ret.add(pkg.namespace_, lib.name, lib); | |||||
} | |||||
} | |||||
return ret; | |||||
} | |||||
std::vector<fs::path> usage_requirement_map::link_paths(std::string ns, std::string name) const { | |||||
auto req = get(ns, name); | |||||
if (!req) { | |||||
throw std::runtime_error( | |||||
fmt::format("Unable to find linking requirement '{}/{}'", ns, name)); | |||||
} | |||||
std::vector<fs::path> ret; | |||||
if (req->linkable_path) { | |||||
ret.push_back(*req->linkable_path); | |||||
} | |||||
for (const auto& dep : req->uses) { | |||||
extend(ret, link_paths(dep.namespace_, dep.name)); | |||||
} | |||||
for (const auto& link : req->links) { | |||||
extend(ret, link_paths(link.namespace_, link.name)); | |||||
} | |||||
return ret; | |||||
} |
#pragma once | |||||
#include <dds/util/fs.hpp> | |||||
#include <libman/index.hpp> | |||||
#include <libman/library.hpp> | |||||
#include <map> | |||||
#include <string> | |||||
namespace dds { | |||||
class shared_compile_file_rules; | |||||
class usage_requirement_map { | |||||
struct library_key { | |||||
std::string namespace_; | |||||
std::string name; | |||||
}; | |||||
struct library_key_compare { | |||||
bool operator()(const library_key& lhs, const library_key& rhs) const noexcept { | |||||
if (lhs.namespace_ < rhs.namespace_) { | |||||
return true; | |||||
} | |||||
if (lhs.namespace_ > rhs.namespace_) { | |||||
return false; | |||||
} | |||||
if (lhs.name < rhs.name) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
}; | |||||
std::map<library_key, lm::library, library_key_compare> _reqs; | |||||
public: | |||||
const lm::library* get(std::string ns, std::string name) const noexcept; | |||||
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 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; | |||||
static usage_requirement_map from_lm_index(const lm::index&) noexcept; | |||||
}; | |||||
} // namespace dds |
library ret; | library ret; | ||||
std::vector<std::string> uses_strs; | |||||
std::vector<std::string> links_strs; | |||||
std::string _type_; | std::string _type_; | ||||
read(fmt::format("Reading library manifest file '{}'", fpath.string()), | read(fmt::format("Reading library manifest file '{}'", fpath.string()), | ||||
pairs, | pairs, | ||||
read_opt("Path", ret.linkable_path), | read_opt("Path", ret.linkable_path), | ||||
read_accumulate("Include-Path", ret.include_paths), | read_accumulate("Include-Path", ret.include_paths), | ||||
read_accumulate("Preprocessor-Define", ret.preproc_defs), | read_accumulate("Preprocessor-Define", ret.preproc_defs), | ||||
read_accumulate("Uses", ret.uses), | |||||
read_accumulate("Uses", uses_strs), | |||||
read_accumulate("Links", links_strs), | |||||
read_accumulate("Special-Uses", ret.special_uses)); | read_accumulate("Special-Uses", ret.special_uses)); | ||||
auto split_req = [](auto&& str) {}; | |||||
for (auto&& uses_str : uses_strs) { | |||||
ret.uses.push_back(split_usage_string(uses_str)); | |||||
} | |||||
for (auto&& links_str : links_strs) { | |||||
ret.links.push_back(split_usage_string(links_str)); | |||||
} | |||||
auto make_absolute = [&](path_ref p) { return fpath.parent_path() / p; }; | auto make_absolute = [&](path_ref p) { return fpath.parent_path() / p; }; | ||||
std::transform(ret.include_paths.begin(), | std::transform(ret.include_paths.begin(), | ||||
ret.include_paths.end(), | ret.include_paths.end(), | ||||
} | } | ||||
return ret; | return ret; | ||||
} | |||||
usage lm::split_usage_string(std::string_view str) { | |||||
auto sl_pos = str.find('/'); | |||||
if (sl_pos == str.npos) { | |||||
throw std::runtime_error("Invalid Uses/Links specifier: " + std::string(str)); | |||||
} | |||||
auto ns = str.substr(0, sl_pos); | |||||
auto name = str.substr(sl_pos + 1); | |||||
return usage{std::string(ns), std::string(name)}; | |||||
} | } |
#include <optional> | #include <optional> | ||||
#include <string> | #include <string> | ||||
#include <string_view> | |||||
namespace lm { | namespace lm { | ||||
struct usage { | |||||
std::string namespace_; | |||||
std::string name; | |||||
}; | |||||
usage split_usage_string(std::string_view); | |||||
class library { | class library { | ||||
public: | public: | ||||
std::string name; | std::string name; | ||||
std::optional<fs::path> linkable_path; | std::optional<fs::path> linkable_path; | ||||
std::vector<fs::path> include_paths; | std::vector<fs::path> include_paths; | ||||
std::vector<std::string> preproc_defs; | std::vector<std::string> preproc_defs; | ||||
std::vector<std::string> uses; | |||||
std::vector<std::string> links; | |||||
std::vector<usage> uses; | |||||
std::vector<usage> links; | |||||
std::vector<std::string> special_uses; | std::vector<std::string> special_uses; | ||||
static library from_file(path_ref); | static library from_file(path_ref); |