| } | } | ||||
| }; | }; | ||||
| 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. | ||||
| */ | */ |