} | } | ||||
}; | }; | ||||
void load_project_deps(dds::builder& bd, | |||||
const dds::package_manifest& man, | |||||
dds::path_ref cat_path, | |||||
dds::path_ref repo_path) { | |||||
auto cat = dds::catalog::open(cat_path); | |||||
// Build the dependencies | |||||
dds::repository::with_repository( // | |||||
repo_path, | |||||
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | |||||
[&](dds::repository repo) { | |||||
// Download dependencies | |||||
auto deps = repo.solve(man.dependencies, cat); | |||||
dds::get_all(deps, repo, cat); | |||||
for (const dds::package_id& pk : deps) { | |||||
auto sdist_ptr = repo.find(pk); | |||||
assert(sdist_ptr); | |||||
dds::sdist_build_params deps_params; | |||||
deps_params.subdir | |||||
= dds::fs::path("_deps") / sdist_ptr->manifest.pkg_id.to_string(); | |||||
bd.add(*sdist_ptr, deps_params); | |||||
} | |||||
}); | |||||
} | |||||
dds::builder create_project_builder(dds::path_ref pr_dir, | |||||
dds::path_ref cat_path, | |||||
dds::path_ref repo_path, | |||||
bool load_deps, | |||||
const dds::sdist_build_params& project_params) { | |||||
auto man = dds::package_manifest::load_from_directory(pr_dir).value_or(dds::package_manifest{}); | |||||
dds::builder builder; | |||||
if (load_deps) { | |||||
load_project_deps(builder, man, cat_path, repo_path); | |||||
} | |||||
builder.add(dds::sdist{std::move(man), pr_dir}, project_params); | |||||
return builder; | |||||
} | |||||
/* | |||||
###### ####### ## ## ######## #### ## ######## | |||||
## ## ## ## ### ### ## ## ## ## ## | |||||
## ## ## #### #### ## ## ## ## ## | |||||
## ## ## ## ### ## ######## ## ## ###### | |||||
## ## ## ## ## ## ## ## ## | |||||
## ## ## ## ## ## ## ## ## ## | |||||
###### ####### ## ## ## #### ######## ######## | |||||
*/ | |||||
struct cli_compile_file { | |||||
cli_base& base; | |||||
args::Command cmd{base.cmd_group, "compile-file", "Compile a single file"}; | |||||
common_flags _flags{cmd}; | |||||
common_project_flags project{cmd}; | |||||
catalog_path_flag cat_path{cmd}; | |||||
repo_path_flag repo_path{cmd}; | |||||
args::Flag no_warnings{cmd, "no-warnings", "Disable compiler warnings", {"no-warnings"}}; | |||||
toolchain_flag tc_filepath{cmd}; | |||||
path_flag | |||||
lm_index{cmd, | |||||
"lm_index", | |||||
"Path to an existing libman index from which to load deps (usually INDEX.lmi)", | |||||
{"lm-index", 'I'}}; | |||||
num_jobs_flag n_jobs{cmd}; | |||||
path_flag out{cmd, | |||||
"out", | |||||
"The root build directory", | |||||
{"out"}, | |||||
dds::fs::current_path() / "_build"}; | |||||
args::PositionalList<dds::fs::path> source_files{cmd, | |||||
"source-files", | |||||
"One or more source files to compile"}; | |||||
int run() { | |||||
dds::sdist_build_params main_params = { | |||||
.subdir = "", | |||||
.build_tests = true, | |||||
.build_apps = true, | |||||
.enable_warnings = !no_warnings.Get(), | |||||
}; | |||||
auto bd = create_project_builder(project.root.Get(), | |||||
cat_path.Get(), | |||||
repo_path.Get(), | |||||
/* load_deps = */ !lm_index, | |||||
main_params); | |||||
bd.compile_files(source_files.Get(), | |||||
{ | |||||
.out_root = out.Get(), | |||||
.existing_lm_index | |||||
= lm_index ? std::make_optional(lm_index.Get()) : std::nullopt, | |||||
.emit_lmi = {}, | |||||
.toolchain = tc_filepath.get_toolchain(), | |||||
.parallel_jobs = n_jobs.Get(), | |||||
}); | |||||
return 0; | |||||
} | |||||
}; | |||||
/* | /* | ||||
######## ## ## #### ## ######## | ######## ## ## #### ## ######## | ||||
## ## ## ## ## ## ## ## | ## ## ## ## ## ## ## ## | ||||
dds::fs::current_path() / "_build"}; | dds::fs::current_path() / "_build"}; | ||||
int run() { | int run() { | ||||
dds::build_params params; | |||||
params.out_root = out.Get(); | |||||
params.toolchain = tc_filepath.get_toolchain(); | |||||
params.parallel_jobs = n_jobs.Get(); | |||||
auto man = dds::package_manifest::load_from_directory(project.root.Get()) | |||||
.value_or(dds::package_manifest{}); | |||||
dds::builder bd; | |||||
dds::sdist_build_params main_params; | |||||
main_params.build_apps = !no_apps.Get(); | |||||
main_params.enable_warnings = !no_warnings.Get(); | |||||
main_params.run_tests = main_params.build_tests = !no_tests.Get(); | |||||
bd.add(dds::sdist{man, project.root.Get()}, main_params); | |||||
if (lm_index) { | |||||
params.existing_lm_index = lm_index.Get(); | |||||
} else { | |||||
// Download and build dependencies | |||||
// Build the dependencies | |||||
auto cat = cat_path.open(); | |||||
dds::repository::with_repository( // | |||||
this->repo_path.Get(), | |||||
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | |||||
[&](dds::repository repo) { | |||||
// Download dependencies | |||||
auto deps = repo.solve(man.dependencies, cat); | |||||
dds::get_all(deps, repo, cat); | |||||
for (const dds::package_id& pk : deps) { | |||||
auto sdist_ptr = repo.find(pk); | |||||
assert(sdist_ptr); | |||||
dds::sdist_build_params deps_params; | |||||
deps_params.subdir | |||||
= dds::fs::path("_deps") / sdist_ptr->manifest.pkg_id.to_string(); | |||||
bd.add(*sdist_ptr, deps_params); | |||||
} | |||||
}); | |||||
} | |||||
bd.build(params); | |||||
dds::sdist_build_params main_params = { | |||||
.subdir = "", | |||||
.build_tests = !no_tests.Get(), | |||||
.run_tests = !no_tests.Get(), | |||||
.build_apps = !no_apps.Get(), | |||||
.enable_warnings = !no_warnings.Get(), | |||||
}; | |||||
auto bd = create_project_builder(project.root.Get(), | |||||
cat_path.Get(), | |||||
repo_path.Get(), | |||||
/* load_deps = */ !lm_index, | |||||
main_params); | |||||
bd.build({ | |||||
.out_root = out.Get(), | |||||
.existing_lm_index = lm_index ? std::make_optional(lm_index.Get()) : std::nullopt, | |||||
.emit_lmi = {}, | |||||
.toolchain = tc_filepath.get_toolchain(), | |||||
.parallel_jobs = n_jobs.Get(), | |||||
}); | |||||
return 0; | return 0; | ||||
} | } | ||||
}; | }; | ||||
spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v"); | spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v"); | ||||
args::ArgumentParser parser("DDS - The drop-dead-simple library manager"); | args::ArgumentParser parser("DDS - The drop-dead-simple library manager"); | ||||
cli_base cli{parser}; | |||||
cli_build build{cli}; | |||||
cli_sdist sdist{cli}; | |||||
cli_repo repo{cli}; | |||||
cli_catalog catalog{cli}; | |||||
cli_build_deps build_deps{cli}; | |||||
cli_base cli{parser}; | |||||
cli_compile_file compile_file{cli}; | |||||
cli_build build{cli}; | |||||
cli_sdist sdist{cli}; | |||||
cli_repo repo{cli}; | |||||
cli_catalog catalog{cli}; | |||||
cli_build_deps build_deps{cli}; | |||||
try { | try { | ||||
parser.ParseCLI(argc, argv); | parser.ParseCLI(argc, argv); | ||||
if (cli._verify_ident) { | if (cli._verify_ident) { | ||||
std::cout << "yes\n"; | std::cout << "yes\n"; | ||||
return 0; | return 0; | ||||
} else if (compile_file.cmd) { | |||||
return compile_file.run(); | |||||
} else if (build.cmd) { | } else if (build.cmd) { | ||||
return build.run(); | return build.run(); | ||||
} else if (sdist.cmd) { | } else if (sdist.cmd) { |
} | } | ||||
} | } | ||||
} // namespace | |||||
void builder::build(const build_params& params) const { | |||||
template <typename Func> | |||||
void with_build_plan(const build_params& params, | |||||
const std::vector<sdist_target>& sdists, | |||||
Func&& fn) { | |||||
fs::create_directories(params.out_root); | fs::create_directories(params.out_root); | ||||
auto db = database::open(params.out_root / ".dds.db"); | auto db = database::open(params.out_root / ".dds.db"); | ||||
state st; | state st; | ||||
auto plan = prepare_build_plan(st, _sdists); | |||||
auto plan = prepare_build_plan(st, sdists); | |||||
auto ureqs = prepare_ureqs(plan, params.toolchain, params.out_root); | auto ureqs = prepare_ureqs(plan, params.toolchain, params.out_root); | ||||
build_env env{ | build_env env{ | ||||
params.toolchain, | params.toolchain, | ||||
plan.render_all(env); | plan.render_all(env); | ||||
dds::stopwatch sw; | |||||
plan.compile_all(env, params.parallel_jobs); | |||||
dds_log(info, "Compilation completed in {:L}ms", sw.elapsed_ms().count()); | |||||
fn(std::move(env), std::move(plan)); | |||||
} | |||||
sw.reset(); | |||||
plan.archive_all(env, params.parallel_jobs); | |||||
dds_log(info, "Archiving completed in {:L}ms", sw.elapsed_ms().count()); | |||||
} // namespace | |||||
sw.reset(); | |||||
plan.link_all(env, params.parallel_jobs); | |||||
dds_log(info, "Runtime binary linking completed in {:L}ms", sw.elapsed_ms().count()); | |||||
void builder::compile_files(const std::vector<fs::path>& files, const build_params& params) const { | |||||
with_build_plan(params, _sdists, [&](build_env_ref env, build_plan plan) { | |||||
plan.compile_files(env, params.parallel_jobs, files); | |||||
}); | |||||
} | |||||
sw.reset(); | |||||
auto test_failures = plan.run_all_tests(env, params.parallel_jobs); | |||||
dds_log(info, "Test execution finished in {:L}ms", sw.elapsed_ms().count()); | |||||
void builder::build(const build_params& params) const { | |||||
with_build_plan(params, _sdists, [&](build_env_ref env, const build_plan& plan) { | |||||
dds::stopwatch sw; | |||||
plan.compile_all(env, params.parallel_jobs); | |||||
dds_log(info, "Compilation completed in {:L}ms", sw.elapsed_ms().count()); | |||||
for (auto& fail : test_failures) { | |||||
log_failure(fail); | |||||
} | |||||
if (!test_failures.empty()) { | |||||
throw_user_error<errc::test_failure>(); | |||||
} | |||||
sw.reset(); | |||||
plan.archive_all(env, params.parallel_jobs); | |||||
dds_log(info, "Archiving completed in {:L}ms", sw.elapsed_ms().count()); | |||||
if (params.emit_lmi) { | |||||
write_lmi(env, plan, params.out_root, *params.emit_lmi); | |||||
} | |||||
sw.reset(); | |||||
plan.link_all(env, params.parallel_jobs); | |||||
dds_log(info, "Runtime binary linking completed in {:L}ms", sw.elapsed_ms().count()); | |||||
sw.reset(); | |||||
auto test_failures = plan.run_all_tests(env, params.parallel_jobs); | |||||
dds_log(info, "Test execution finished in {:L}ms", sw.elapsed_ms().count()); | |||||
for (auto& fail : test_failures) { | |||||
log_failure(fail); | |||||
} | |||||
if (!test_failures.empty()) { | |||||
throw_user_error<errc::test_failure>(); | |||||
} | |||||
if (params.emit_lmi) { | |||||
write_lmi(env, plan, params.out_root, *params.emit_lmi); | |||||
} | |||||
}); | |||||
} | } |
* Execute the build | * Execute the build | ||||
*/ | */ | ||||
void build(const build_params& params) const; | void build(const build_params& params) const; | ||||
/** | |||||
* Compile one or more source files | |||||
*/ | |||||
void compile_files(const std::vector<fs::path>& files, const build_params& params) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
#include <dds/build/iter_compilations.hpp> | #include <dds/build/iter_compilations.hpp> | ||||
#include <dds/build/plan/compile_exec.hpp> | #include <dds/build/plan/compile_exec.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/util/log.hpp> | |||||
#include <dds/util/parallel.hpp> | #include <dds/util/parallel.hpp> | ||||
#include <range/v3/algorithm/any_of.hpp> | |||||
#include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
#include <range/v3/view/concat.hpp> | #include <range/v3/view/concat.hpp> | ||||
#include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
} | } | ||||
} | } | ||||
void build_plan::compile_files(const build_env& env, | |||||
int njobs, | |||||
const std::vector<fs::path>& filepaths) const { | |||||
struct pending_file { | |||||
bool marked = false; | |||||
fs::path filepath; | |||||
}; | |||||
auto as_pending = // | |||||
ranges::view::all(filepaths) // | |||||
| ranges::view::transform([](auto&& path) { | |||||
return pending_file{false, fs::weakly_canonical(path)}; | |||||
}) | |||||
| ranges::to_vector; | |||||
auto check_compilation = [&](const compile_file_plan& comp) { | |||||
return ranges::any_of(as_pending, [&](pending_file& f) { | |||||
bool same_file = f.filepath == fs::weakly_canonical(comp.source_path()); | |||||
if (same_file) { | |||||
f.marked = true; | |||||
} | |||||
return same_file; | |||||
}); | |||||
}; | |||||
auto comps | |||||
= iter_compilations(*this) | ranges::view::filter(check_compilation) | ranges::to_vector; | |||||
bool any_unmarked = false; | |||||
auto unmarked = ranges::view::filter(as_pending, ranges::not_fn(&pending_file::marked)); | |||||
for (auto&& um : unmarked) { | |||||
dds_log(error, "Source file [{}] is not compiled by this project", um.filepath.string()); | |||||
any_unmarked = true; | |||||
} | |||||
if (any_unmarked) { | |||||
throw_user_error<errc::compile_failure>( | |||||
"One or more requested files is not part of this project (See above)"); | |||||
} | |||||
auto okay = dds::compile_all(comps, env, njobs); | |||||
if (!okay) { | |||||
throw_user_error<errc::compile_failure>(); | |||||
} | |||||
} | |||||
void build_plan::archive_all(const build_env& env, int njobs) const { | void build_plan::archive_all(const build_env& env, int njobs) const { | ||||
auto okay = parallel_run(iter_libraries(*this), njobs, [&](const library_plan& lib) { | auto okay = parallel_run(iter_libraries(*this), njobs, [&](const library_plan& lib) { | ||||
if (lib.archive_plan()) { | if (lib.archive_plan()) { |
*/ | */ | ||||
void link_all(const build_env& env, int njobs) const; | void link_all(const build_env& env, int njobs) const; | ||||
/** | |||||
* Compile the files given in the vector of file paths. | |||||
*/ | |||||
void compile_files(const build_env& env, int njobs, const std::vector<fs::path>& paths) const; | |||||
/** | /** | ||||
* Execute all tests defined in the plan. Returns information for every failed test. | * Execute all tests defined in the plan. Returns information for every failed test. | ||||
*/ | */ |