@@ -1,21 +1,20 @@ | |||
#include "./build.hpp" | |||
#include <dds/build/compile.hpp> | |||
#include <dds/build/plan/compile_file.hpp> | |||
#include <dds/logging.hpp> | |||
#include <dds/proc.hpp> | |||
#include <dds/project.hpp> | |||
#include <dds/source.hpp> | |||
#include <dds/toolchain.hpp> | |||
#include <dds/usage_reqs.hpp> | |||
#include <dds/util/algo.hpp> | |||
#include <dds/util/string.hpp> | |||
#include <libman/index.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/view/drop.hpp> | |||
#include <range/v3/view/filter.hpp> | |||
#include <range/v3/view/join.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <algorithm> | |||
@@ -103,10 +102,10 @@ fs::path export_project_library(const build_params& params, | |||
} | |||
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) { | |||
pairs.emplace_back("Links", links); | |||
pairs.emplace_back("Links", fmt::format("{}/{}", links.namespace_, links.name)); | |||
} | |||
lm::write_pairs(lml_path, pairs); | |||
@@ -141,322 +140,79 @@ void export_project(const build_params& params, const project& project) { | |||
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 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; | |||
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); | |||
// } | |||
} |
@@ -1,123 +0,0 @@ | |||
#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"); | |||
} | |||
} |
@@ -3,11 +3,10 @@ | |||
#include <dds/proc.hpp> | |||
#include <range/v3/action/join.hpp> | |||
#include <range/v3/view/concat.hpp> | |||
#include <range/v3/view/filter.hpp> | |||
#include <range/v3/view/join.hpp> | |||
#include <range/v3/view/repeat_n.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <range/v3/view/zip.hpp> | |||
#include <spdlog/spdlog.h> | |||
@@ -17,62 +16,6 @@ | |||
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 { | |||
template <typename Range, typename Fn> | |||
@@ -129,53 +72,38 @@ bool parallel_run(Range&& rng, int n_jobs, Fn&& fn) { | |||
} // 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 { | |||
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 | |||
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) { | |||
throw std::runtime_error("Compilation failed."); | |||
} | |||
@@ -183,17 +111,16 @@ void build_plan::compile_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) { | |||
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); | |||
} | |||
} | |||
} |
@@ -1,56 +1,26 @@ | |||
#pragma once | |||
#include <dds/build/compile.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/toolchain.hpp> | |||
#include <dds/util/fs.hpp> | |||
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 |
@@ -0,0 +1,42 @@ | |||
#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)); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
#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 |
@@ -0,0 +1,15 @@ | |||
#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 |
@@ -0,0 +1,66 @@ | |||
#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; | |||
} |
@@ -1,20 +1,12 @@ | |||
#pragma once | |||
#include <dds/build/plan/base.hpp> | |||
#include <dds/source.hpp> | |||
#include <dds/toolchain.hpp> | |||
#include <dds/util/fs.hpp> | |||
#include <memory> | |||
#include <optional> | |||
#include <stdexcept> | |||
namespace dds { | |||
struct build_env { | |||
dds::toolchain toolchain; | |||
fs::path output_root; | |||
}; | |||
struct compile_failure : std::runtime_error { | |||
using runtime_error::runtime_error; | |||
}; | |||
@@ -41,16 +33,27 @@ public: | |||
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 |
@@ -0,0 +1,41 @@ | |||
#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)); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
#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 |
@@ -0,0 +1,11 @@ | |||
#pragma once | |||
#include <build/plan/package.hpp> | |||
#include <map> | |||
namespace dds { | |||
class build_plan2 {}; | |||
} // namespace dds |
@@ -0,0 +1,85 @@ | |||
#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)}; | |||
} |
@@ -0,0 +1,41 @@ | |||
#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 |
@@ -0,0 +1,27 @@ | |||
#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 |
@@ -375,10 +375,11 @@ struct cli_deps { | |||
man.dependencies.end()); | |||
}); | |||
auto plan = dds::create_deps_build_plan(deps); | |||
auto tc = tc_filepath.get_toolchain(); | |||
auto bdir = build_dir.Get(); | |||
dds::build_env env{std::move(tc), bdir}; | |||
auto plan = dds::create_deps_build_plan(deps, env); | |||
plan.compile_all(env, 6); | |||
plan.archive_all(env, 6); | |||
if (!no_lmi.Get()) { |
@@ -2,6 +2,8 @@ | |||
#include <dds/repo/repo.hpp> | |||
#include <dds/sdist.hpp> | |||
#include <dds/usage_reqs.hpp> | |||
#include <dds/util/algo.hpp> | |||
#include <dds/util/string.hpp> | |||
#include <libman/index.hpp> | |||
#include <libman/parse.hpp> | |||
@@ -72,12 +74,13 @@ void detail::do_find_deps(const repository& repo, const dependency& dep, std::ve | |||
} | |||
using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | |||
using sdist_names = std::set<std::string>; | |||
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) { | |||
auto found = sd_idx.find(dep.name); | |||
if (found == sd_idx.end()) { | |||
@@ -86,7 +89,7 @@ void linkup_dependencies(shared_compile_file_rules& rules, | |||
dep.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_include = found->second.get().path / "include"; | |||
if (fs::exists(lib_include)) { | |||
@@ -97,32 +100,75 @@ void linkup_dependencies(shared_compile_file_rules& rules, | |||
} | |||
} | |||
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) { | |||
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 | |||
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>(); | |||
build_plan plan; | |||
build_plan plan; | |||
usage_requirement_map ureqs; | |||
sdist_names already_added; | |||
for (const sdist& sd : deps) { | |||
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; | |||
} | |||
@@ -130,20 +176,20 @@ build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps) { | |||
namespace { | |||
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; | |||
std::vector<lm::pair> kvs; | |||
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", | |||
fs::relative(lib.create_archive->archive_file_path(env), | |||
fs::relative(lib.create_archive()->calc_archive_file_path(env), | |||
lml_path.parent_path()) | |||
.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)) { | |||
pub_inc_dir = src_dir; | |||
} | |||
@@ -157,16 +203,16 @@ fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_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; | |||
std::vector<lm::pair> kvs; | |||
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()); | |||
} | |||
@@ -184,11 +230,11 @@ void dds::write_libman_index(path_ref out_filepath, const build_plan& plan, cons | |||
auto lm_items_dir = out_filepath.parent_path() / "_libman"; | |||
std::vector<lm::pair> kvs; | |||
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); | |||
kvs.emplace_back("Package", | |||
fmt::format("{}; {}", | |||
pkg.name, | |||
pkg.name(), | |||
fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | |||
} | |||
lm::write_pairs(out_filepath, kvs); |
@@ -42,7 +42,7 @@ inline std::vector<sdist> find_dependencies(const repository& repo, Iter it, Snt | |||
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); | |||
@@ -1,6 +1,6 @@ | |||
#include <dds/library.hpp> | |||
#include <dds/build/compile.hpp> | |||
#include <dds/build/plan/compile_file.hpp> | |||
#include <dds/build/source_dir.hpp> | |||
#include <dds/util/algo.hpp> | |||
@@ -1,6 +1,6 @@ | |||
#pragma once | |||
#include <dds/build/compile.hpp> | |||
#include <dds/build/plan/compile_file.hpp> | |||
#include <dds/build/source_dir.hpp> | |||
#include <dds/library_manifest.hpp> | |||
#include <dds/source.hpp> | |||
@@ -42,11 +42,9 @@ public: | |||
}; | |||
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); |
@@ -1,5 +1,8 @@ | |||
#include "./library_manifest.hpp" | |||
#include <dds/util/algo.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <libman/parse.hpp> | |||
#include <spdlog/fmt/fmt.h> | |||
@@ -10,13 +13,16 @@ library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||
auto kvs = lm::parse_file(fpath); | |||
library_manifest ret; | |||
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()), | |||
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()); | |||
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; | |||
} |
@@ -2,16 +2,16 @@ | |||
#include <dds/util/fs.hpp> | |||
#include <libman/library.hpp> | |||
#include <vector> | |||
namespace dds { | |||
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&); | |||
}; |
@@ -0,0 +1,74 @@ | |||
#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; | |||
} |
@@ -0,0 +1,49 @@ | |||
#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 |
@@ -11,6 +11,9 @@ library library::from_file(path_ref fpath) { | |||
library ret; | |||
std::vector<std::string> uses_strs; | |||
std::vector<std::string> links_strs; | |||
std::string _type_; | |||
read(fmt::format("Reading library manifest file '{}'", fpath.string()), | |||
pairs, | |||
@@ -20,9 +23,20 @@ library library::from_file(path_ref fpath) { | |||
read_opt("Path", ret.linkable_path), | |||
read_accumulate("Include-Path", ret.include_paths), | |||
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)); | |||
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; }; | |||
std::transform(ret.include_paths.begin(), | |||
ret.include_paths.end(), | |||
@@ -34,4 +48,14 @@ library library::from_file(path_ref fpath) { | |||
} | |||
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)}; | |||
} |
@@ -4,17 +4,25 @@ | |||
#include <optional> | |||
#include <string> | |||
#include <string_view> | |||
namespace lm { | |||
struct usage { | |||
std::string namespace_; | |||
std::string name; | |||
}; | |||
usage split_usage_string(std::string_view); | |||
class library { | |||
public: | |||
std::string name; | |||
std::optional<fs::path> linkable_path; | |||
std::vector<fs::path> include_paths; | |||
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; | |||
static library from_file(path_ref); |