You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

906 satır
31KB

  1. #include <dds/build/builder.hpp>
  2. #include <dds/catalog/catalog.hpp>
  3. #include <dds/catalog/get.hpp>
  4. #include <dds/dym.hpp>
  5. #include <dds/error/errors.hpp>
  6. #include <dds/repo/repo.hpp>
  7. #include <dds/source/dist.hpp>
  8. #include <dds/toolchain/from_json.hpp>
  9. #include <dds/util/fs.hpp>
  10. #include <dds/util/log.hpp>
  11. #include <dds/util/paths.hpp>
  12. #include <dds/util/signal.hpp>
  13. #include <range/v3/action/join.hpp>
  14. #include <range/v3/range/conversion.hpp>
  15. #include <range/v3/view/concat.hpp>
  16. #include <range/v3/view/group_by.hpp>
  17. #include <range/v3/view/transform.hpp>
  18. #include <spdlog/spdlog.h>
  19. #include <dds/3rd/args.hxx>
  20. #include <filesystem>
  21. #include <iostream>
  22. #include <sstream>
  23. namespace {
  24. using string_flag = args::ValueFlag<std::string>;
  25. using path_flag = args::ValueFlag<dds::fs::path>;
  26. struct toolchain_flag : string_flag {
  27. toolchain_flag(args::Group& grp)
  28. : string_flag{grp,
  29. "toolchain_file",
  30. "Path/identifier of the toolchain to use",
  31. {"toolchain", 't'}} {}
  32. dds::toolchain get_toolchain() {
  33. if (*this) {
  34. return get_arg();
  35. } else {
  36. auto found = dds::toolchain::get_default();
  37. if (!found) {
  38. dds::throw_user_error<dds::errc::no_default_toolchain>();
  39. }
  40. return *found;
  41. }
  42. }
  43. dds::toolchain get_arg() {
  44. const auto tc_path = this->Get();
  45. if (tc_path.find(":") == 0) {
  46. auto default_tc = tc_path.substr(1);
  47. auto tc = dds::toolchain::get_builtin(default_tc);
  48. if (!tc.has_value()) {
  49. dds::throw_user_error<
  50. dds::errc::invalid_builtin_toolchain>("Invalid built-in toolchain name '{}'",
  51. default_tc);
  52. }
  53. return std::move(*tc);
  54. } else {
  55. return dds::parse_toolchain_json5(dds::slurp_file(tc_path));
  56. // return dds::parse_toolchain_dds(dds::slurp_file(tc_path));
  57. }
  58. }
  59. };
  60. struct repo_path_flag : path_flag {
  61. repo_path_flag(args::Group& grp)
  62. : path_flag{grp,
  63. "dir",
  64. "Path to the DDS repository directory",
  65. {"repo-dir"},
  66. dds::repository::default_local_path()} {}
  67. };
  68. struct catalog_path_flag : path_flag {
  69. catalog_path_flag(args::Group& cmd)
  70. : path_flag(cmd,
  71. "catalog-path",
  72. "Override the path to the catalog database",
  73. {"catalog", 'c'},
  74. dds::dds_data_dir() / "catalog.db") {}
  75. dds::catalog open() { return dds::catalog::open(Get()); }
  76. };
  77. struct num_jobs_flag : args::ValueFlag<int> {
  78. num_jobs_flag(args::Group& cmd)
  79. : ValueFlag(cmd,
  80. "jobs",
  81. "Set the number of parallel jobs when compiling files",
  82. {"jobs", 'j'},
  83. 0) {}
  84. };
  85. /**
  86. * Base class holds the actual argument parser
  87. */
  88. struct cli_base {
  89. args::ArgumentParser& parser;
  90. args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}};
  91. // Test argument:
  92. args::Flag _verify_ident{parser,
  93. "test",
  94. "Print `yes` and exit 0. Useful for scripting.",
  95. {"are-you-the-real-dds?"}};
  96. args::MapFlag<std::string, dds::log::level> log_level{
  97. parser,
  98. "log-level",
  99. "Set the logging level",
  100. {"log-level", 'l'},
  101. {
  102. {"trace", dds::log::level::trace},
  103. {"debug", dds::log::level::debug},
  104. {"info", dds::log::level::info},
  105. {"warn", dds::log::level::warn},
  106. {"error", dds::log::level::error},
  107. {"critical", dds::log::level::critical},
  108. },
  109. dds::log::level::info,
  110. };
  111. args::Group cmd_group{parser, "Available Commands"};
  112. };
  113. /**
  114. * Flags common to all subcommands
  115. */
  116. struct common_flags {
  117. args::Command& cmd;
  118. args::HelpFlag _help{cmd, "help", "Print this help message and exit", {'h', "help"}};
  119. };
  120. /**
  121. * Flags common to project-related commands
  122. */
  123. struct common_project_flags {
  124. args::Command& cmd;
  125. path_flag root{cmd,
  126. "project_dir",
  127. "Path to the directory containing the project",
  128. {'p', "project-dir"},
  129. dds::fs::current_path()};
  130. };
  131. /*
  132. ###### ### ######## ### ## ####### ######
  133. ## ## ## ## ## ## ## ## ## ## ## ##
  134. ## ## ## ## ## ## ## ## ## ##
  135. ## ## ## ## ## ## ## ## ## ## ####
  136. ## ######### ## ######### ## ## ## ## ##
  137. ## ## ## ## ## ## ## ## ## ## ## ##
  138. ###### ## ## ## ## ## ######## ####### ######
  139. */
  140. struct cli_catalog {
  141. cli_base& base;
  142. args::Command cmd{base.cmd_group, "catalog", "Manage the package catalog"};
  143. common_flags _common{cmd};
  144. args::Group cat_group{cmd, "Catalog subcommands"};
  145. struct {
  146. cli_catalog& parent;
  147. args::Command cmd{parent.cat_group, "create", "Create a catalog database"};
  148. common_flags _common{cmd};
  149. catalog_path_flag cat_path{cmd};
  150. int run() {
  151. // Simply opening the DB will initialize the catalog
  152. cat_path.open();
  153. return 0;
  154. }
  155. } create{*this};
  156. struct {
  157. cli_catalog& parent;
  158. args::Command cmd{parent.cat_group, "import", "Import entries into a catalog"};
  159. common_flags _common{cmd};
  160. catalog_path_flag cat_path{cmd};
  161. args::Flag import_stdin{cmd, "stdin", "Import JSON from stdin", {"stdin"}};
  162. args::Flag init{cmd, "initial", "Re-import the initial catalog contents", {"initial"}};
  163. args::ValueFlagList<std::string>
  164. json_paths{cmd,
  165. "json",
  166. "Import catalog entries from the given JSON files",
  167. {"json", 'j'}};
  168. int run() {
  169. auto cat = cat_path.open();
  170. if (init.Get()) {
  171. cat.import_initial();
  172. }
  173. for (const auto& json_fpath : json_paths.Get()) {
  174. cat.import_json_file(json_fpath);
  175. }
  176. if (import_stdin.Get()) {
  177. std::ostringstream strm;
  178. strm << std::cin.rdbuf();
  179. cat.import_json_str(strm.str());
  180. }
  181. return 0;
  182. }
  183. } import{*this};
  184. struct {
  185. cli_catalog& parent;
  186. args::Command cmd{parent.cat_group, "get", "Obtain an sdist from a catalog listing"};
  187. common_flags _common{cmd};
  188. catalog_path_flag cat_path{cmd};
  189. path_flag out{cmd,
  190. "out",
  191. "The directory where the source distributions will be placed",
  192. {"out-dir", 'o'},
  193. dds::fs::current_path()};
  194. args::PositionalList<std::string> requirements{cmd,
  195. "requirement",
  196. "The package IDs to obtain"};
  197. int run() {
  198. auto cat = cat_path.open();
  199. for (const auto& req : requirements.Get()) {
  200. auto id = dds::package_id::parse(req);
  201. dds::dym_target dym;
  202. auto info = cat.get(id);
  203. if (!info) {
  204. dds::throw_user_error<dds::errc::no_such_catalog_package>(
  205. "No package in the catalog matched the ID '{}'.{}",
  206. req,
  207. dym.sentence_suffix());
  208. }
  209. auto tsd = dds::get_package_sdist(*info);
  210. auto out_path = out.Get();
  211. auto dest = out_path / id.to_string();
  212. dds_log(info, "Create sdist at {}", dest.string());
  213. dds::fs::remove_all(dest);
  214. dds::safe_rename(tsd.sdist.path, dest);
  215. }
  216. return 0;
  217. }
  218. } get{*this};
  219. struct {
  220. cli_catalog& parent;
  221. args::Command cmd{parent.cat_group, "add", "Manually add an entry to the catalog database"};
  222. common_flags _common{cmd};
  223. catalog_path_flag cat_path{cmd};
  224. args::Positional<std::string> pkg_id{cmd,
  225. "id",
  226. "The name@version ID of the package to add",
  227. args::Options::Required};
  228. string_flag auto_lib{cmd,
  229. "auto-lib",
  230. "Set the auto-library information for this package",
  231. {"auto-lib"}};
  232. args::ValueFlagList<std::string> deps{cmd,
  233. "depends",
  234. "The dependencies of this package",
  235. {"depends", 'd'}};
  236. string_flag git_url{cmd, "git-url", "The Git url for the package", {"git-url"}};
  237. string_flag git_ref{cmd,
  238. "git-ref",
  239. "The Git ref to from which the source distribution should be created",
  240. {"git-ref"}};
  241. string_flag description{cmd, "description", "A description of the package", {"desc"}};
  242. int run() {
  243. auto ident = dds::package_id::parse(pkg_id.Get());
  244. std::vector<dds::dependency> deps;
  245. for (const auto& dep : this->deps.Get()) {
  246. auto dep_id = dds::package_id::parse(dep);
  247. assert(false && "TODO");
  248. // deps.push_back({dep_id.name, dep_id.version});
  249. }
  250. dds::package_info info{ident, std::move(deps), description.Get(), {}};
  251. if (git_url) {
  252. if (!git_ref) {
  253. dds::throw_user_error<dds::errc::git_url_ref_mutual_req>();
  254. }
  255. auto git = dds::git_remote_listing{git_url.Get(), git_ref.Get(), std::nullopt, {}};
  256. if (auto_lib) {
  257. git.auto_lib = lm::split_usage_string(auto_lib.Get());
  258. }
  259. info.remote = std::move(git);
  260. } else if (git_ref) {
  261. dds::throw_user_error<dds::errc::git_url_ref_mutual_req>();
  262. }
  263. cat_path.open().store(info);
  264. return 0;
  265. }
  266. } add{*this};
  267. struct {
  268. cli_catalog& parent;
  269. args::Command cmd{parent.cat_group, "list", "List the contents of the catalog"};
  270. catalog_path_flag cat_path{cmd};
  271. string_flag name{cmd, "name", "Only list packages with the given name", {"name", 'n'}};
  272. int run() {
  273. auto cat = cat_path.open();
  274. auto pkgs = name ? cat.by_name(name.Get()) : cat.all();
  275. for (const dds::package_id& pk : pkgs) {
  276. std::cout << pk.to_string() << '\n';
  277. }
  278. return 0;
  279. }
  280. } list{*this};
  281. struct {
  282. cli_catalog& parent;
  283. args::Command cmd{parent.cat_group,
  284. "show",
  285. "Show information about a single package in the catalog"};
  286. catalog_path_flag cat_path{cmd};
  287. args::Positional<std::string> ident{cmd,
  288. "package-id",
  289. "A package identifier to show",
  290. args::Options::Required};
  291. void print_remote_info(const dds::git_remote_listing& git) {
  292. std::cout << "Git URL: " << git.url << '\n';
  293. std::cout << "Git Ref: " << git.ref << '\n';
  294. if (git.auto_lib) {
  295. std::cout << "Auto-lib: " << git.auto_lib->name << "/" << git.auto_lib->namespace_
  296. << '\n';
  297. }
  298. }
  299. void print_remote_info(std::monostate) {
  300. std::cout << "THIS ENTRY IS MISSING REMOTE INFORMATION!\n";
  301. }
  302. int run() {
  303. auto pk_id = dds::package_id::parse(ident.Get());
  304. auto cat = cat_path.open();
  305. auto pkg = cat.get(pk_id);
  306. if (!pkg) {
  307. dds_log(error, "No package '{}' in the catalog", pk_id.to_string());
  308. return 1;
  309. }
  310. std::cout << "Name: " << pkg->ident.name << '\n'
  311. << "Version: " << pkg->ident.version << '\n';
  312. for (const auto& dep : pkg->deps) {
  313. std::cout << "Depends: " << dep.to_string() << '\n';
  314. }
  315. std::visit([&](const auto& remote) { print_remote_info(remote); }, pkg->remote);
  316. std::cout << "Description:\n " << pkg->description << '\n';
  317. return 0;
  318. }
  319. } show{*this};
  320. int run() {
  321. if (create.cmd) {
  322. return create.run();
  323. } else if (import.cmd) {
  324. return import.run();
  325. } else if (get.cmd) {
  326. return get.run();
  327. } else if (add.cmd) {
  328. return add.run();
  329. } else if (list.cmd) {
  330. return list.run();
  331. } else if (show.cmd) {
  332. return show.run();
  333. } else {
  334. assert(false);
  335. std::terminate();
  336. }
  337. }
  338. };
  339. /*
  340. ######## ######## ######## #######
  341. ## ## ## ## ## ## ##
  342. ## ## ## ## ## ## ##
  343. ######## ###### ######## ## ##
  344. ## ## ## ## ## ##
  345. ## ## ## ## ## ##
  346. ## ## ######## ## #######
  347. */
  348. struct cli_repo {
  349. cli_base& base;
  350. args::Command cmd{base.cmd_group, "repo", "Manage the package repository"};
  351. common_flags _common{cmd};
  352. repo_path_flag where{cmd};
  353. args::Group repo_group{cmd, "Repo subcommands"};
  354. struct {
  355. cli_repo& parent;
  356. args::Command cmd{parent.repo_group, "ls", "List repository contents"};
  357. common_flags _common{cmd};
  358. int run() {
  359. auto list_contents = [&](dds::repository repo) {
  360. auto same_name = [](auto&& a, auto&& b) {
  361. return a.manifest.pkg_id.name == b.manifest.pkg_id.name;
  362. };
  363. auto all = repo.iter_sdists();
  364. auto grp_by_name = all //
  365. | ranges::views::group_by(same_name) //
  366. | ranges::views::transform(ranges::to_vector) //
  367. | ranges::views::transform([](auto&& grp) {
  368. assert(grp.size() > 0);
  369. return std::pair(grp[0].manifest.pkg_id.name, grp);
  370. });
  371. for (const auto& [name, grp] : grp_by_name) {
  372. dds_log(info, "{}:", name);
  373. for (const dds::sdist& sd : grp) {
  374. dds_log(info, " - {}", sd.manifest.pkg_id.version.to_string());
  375. }
  376. }
  377. return 0;
  378. };
  379. return dds::repository::with_repository(parent.where.Get(),
  380. dds::repo_flags::read,
  381. list_contents);
  382. }
  383. } ls{*this};
  384. struct {
  385. cli_repo& parent;
  386. args::Command cmd{parent.repo_group, "init", "Initialize a directory as a repository"};
  387. common_flags _common{cmd};
  388. int run() {
  389. if (parent.where.Get().empty()) {
  390. throw args::ParseError("The --dir flag is required");
  391. }
  392. auto repo_dir = dds::fs::absolute(parent.where.Get());
  393. dds::repository::with_repository(repo_dir, dds::repo_flags::create_if_absent, [](auto) {
  394. });
  395. return 0;
  396. }
  397. } init{*this};
  398. int run() {
  399. if (ls.cmd) {
  400. return ls.run();
  401. } else if (init.cmd) {
  402. return init.run();
  403. } else {
  404. assert(false);
  405. std::terminate();
  406. }
  407. }
  408. };
  409. /*
  410. ###### ######## #### ###### ########
  411. ## ## ## ## ## ## ## ##
  412. ## ## ## ## ## ##
  413. ###### ## ## ## ###### ##
  414. ## ## ## ## ## ##
  415. ## ## ## ## ## ## ## ##
  416. ###### ######## #### ###### ##
  417. */
  418. struct cli_sdist {
  419. cli_base& base;
  420. args::Command cmd{base.cmd_group, "sdist", "Work with source distributions"};
  421. common_flags _common{cmd};
  422. args::Group sdist_group{cmd, "`sdist` commands"};
  423. struct {
  424. cli_sdist& parent;
  425. args::Command cmd{parent.sdist_group, "create", "Create a source distribution"};
  426. common_project_flags project{cmd};
  427. path_flag out{cmd,
  428. "out",
  429. "The destination of the source distribution",
  430. {"out"},
  431. dds::fs::current_path() / "project.dsd"};
  432. args::Flag force{cmd,
  433. "replace-if-exists",
  434. "Forcibly replace an existing distribution",
  435. {"replace"}};
  436. int run() {
  437. dds::sdist_params params;
  438. params.project_dir = project.root.Get();
  439. params.dest_path = out.Get();
  440. params.force = force.Get();
  441. dds::create_sdist(params);
  442. return 0;
  443. }
  444. } create{*this};
  445. struct {
  446. cli_sdist& parent;
  447. args::Command cmd{parent.sdist_group,
  448. "export",
  449. "Export a source distribution to a repository"};
  450. common_project_flags project{cmd};
  451. repo_path_flag repo_where{cmd};
  452. args::Flag force{cmd,
  453. "replace-if-exists",
  454. "Replace an existing export in the repository",
  455. {"replace"}};
  456. int run() {
  457. auto repo_dir = repo_where.Get();
  458. // TODO: Generate a unique name to avoid conflicts
  459. auto tmp_sdist = dds::fs::temp_directory_path() / ".dds-sdist";
  460. if (dds::fs::exists(tmp_sdist)) {
  461. dds::fs::remove_all(tmp_sdist);
  462. }
  463. dds::sdist_params params;
  464. params.project_dir = project.root.Get();
  465. params.dest_path = tmp_sdist;
  466. params.force = true;
  467. auto sdist = dds::create_sdist(params);
  468. dds::repository::with_repository( //
  469. repo_dir,
  470. dds::repo_flags::create_if_absent | dds::repo_flags::write_lock,
  471. [&](dds::repository repo) { //
  472. repo.add_sdist(sdist,
  473. force.Get() ? dds::if_exists::replace
  474. : dds::if_exists::throw_exc);
  475. });
  476. return 0;
  477. }
  478. } export_{*this};
  479. int run() {
  480. if (create.cmd) {
  481. return create.run();
  482. } else if (export_.cmd) {
  483. return export_.run();
  484. } else {
  485. assert(false && "Unreachable");
  486. std::terminate();
  487. }
  488. }
  489. };
  490. void load_project_deps(dds::builder& bd,
  491. const dds::package_manifest& man,
  492. dds::path_ref cat_path,
  493. dds::path_ref repo_path) {
  494. auto cat = dds::catalog::open(cat_path);
  495. // Build the dependencies
  496. dds::repository::with_repository( //
  497. repo_path,
  498. dds::repo_flags::write_lock | dds::repo_flags::create_if_absent,
  499. [&](dds::repository repo) {
  500. // Download dependencies
  501. auto deps = repo.solve(man.dependencies, cat);
  502. dds::get_all(deps, repo, cat);
  503. for (const dds::package_id& pk : deps) {
  504. auto sdist_ptr = repo.find(pk);
  505. assert(sdist_ptr);
  506. dds::sdist_build_params deps_params;
  507. deps_params.subdir
  508. = dds::fs::path("_deps") / sdist_ptr->manifest.pkg_id.to_string();
  509. bd.add(*sdist_ptr, deps_params);
  510. }
  511. });
  512. }
  513. dds::builder create_project_builder(dds::path_ref pr_dir,
  514. dds::path_ref cat_path,
  515. dds::path_ref repo_path,
  516. bool load_deps,
  517. const dds::sdist_build_params& project_params) {
  518. auto man = dds::package_manifest::load_from_directory(pr_dir).value_or(dds::package_manifest{});
  519. dds::builder builder;
  520. if (load_deps) {
  521. load_project_deps(builder, man, cat_path, repo_path);
  522. }
  523. builder.add(dds::sdist{std::move(man), pr_dir}, project_params);
  524. return builder;
  525. }
  526. /*
  527. ###### ####### ## ## ######## #### ## ########
  528. ## ## ## ## ### ### ## ## ## ## ##
  529. ## ## ## #### #### ## ## ## ## ##
  530. ## ## ## ## ### ## ######## ## ## ######
  531. ## ## ## ## ## ## ## ## ##
  532. ## ## ## ## ## ## ## ## ## ##
  533. ###### ####### ## ## ## #### ######## ########
  534. */
  535. struct cli_compile_file {
  536. cli_base& base;
  537. args::Command cmd{base.cmd_group, "compile-file", "Compile a single file"};
  538. common_flags _flags{cmd};
  539. common_project_flags project{cmd};
  540. catalog_path_flag cat_path{cmd};
  541. repo_path_flag repo_path{cmd};
  542. args::Flag no_warnings{cmd, "no-warnings", "Disable compiler warnings", {"no-warnings"}};
  543. toolchain_flag tc_filepath{cmd};
  544. path_flag
  545. lm_index{cmd,
  546. "lm_index",
  547. "Path to an existing libman index from which to load deps (usually INDEX.lmi)",
  548. {"lm-index", 'I'}};
  549. num_jobs_flag n_jobs{cmd};
  550. path_flag out{cmd,
  551. "out",
  552. "The root build directory",
  553. {"out"},
  554. dds::fs::current_path() / "_build"};
  555. args::PositionalList<dds::fs::path> source_files{cmd,
  556. "source-files",
  557. "One or more source files to compile"};
  558. int run() {
  559. dds::sdist_build_params main_params = {
  560. .subdir = "",
  561. .build_tests = true,
  562. .build_apps = true,
  563. .enable_warnings = !no_warnings.Get(),
  564. };
  565. auto bd = create_project_builder(project.root.Get(),
  566. cat_path.Get(),
  567. repo_path.Get(),
  568. /* load_deps = */ !lm_index,
  569. main_params);
  570. bd.compile_files(source_files.Get(),
  571. {
  572. .out_root = out.Get(),
  573. .existing_lm_index
  574. = lm_index ? std::make_optional(lm_index.Get()) : std::nullopt,
  575. .emit_lmi = {},
  576. .toolchain = tc_filepath.get_toolchain(),
  577. .parallel_jobs = n_jobs.Get(),
  578. });
  579. return 0;
  580. }
  581. };
  582. /*
  583. ######## ## ## #### ## ########
  584. ## ## ## ## ## ## ## ##
  585. ## ## ## ## ## ## ## ##
  586. ######## ## ## ## ## ## ##
  587. ## ## ## ## ## ## ## ##
  588. ## ## ## ## ## ## ## ##
  589. ######## ####### #### ######## ########
  590. */
  591. struct cli_build {
  592. cli_base& base;
  593. args::Command cmd{base.cmd_group, "build", "Build a project"};
  594. common_flags _common{cmd};
  595. common_project_flags project{cmd};
  596. catalog_path_flag cat_path{cmd};
  597. repo_path_flag repo_path{cmd};
  598. args::Flag no_tests{cmd, "no-tests", "Do not build and run tests", {"no-tests"}};
  599. args::Flag no_apps{cmd, "no-apps", "Do not compile and link applications", {"no-apps"}};
  600. args::Flag no_warnings{cmd, "no-warings", "Disable build warnings", {"no-warnings"}};
  601. toolchain_flag tc_filepath{cmd};
  602. path_flag
  603. lm_index{cmd,
  604. "lm_index",
  605. "Path to an existing libman index from which to load deps (usually INDEX.lmi)",
  606. {"lm-index", 'I'}};
  607. num_jobs_flag n_jobs{cmd};
  608. path_flag out{cmd,
  609. "out",
  610. "The root build directory",
  611. {"out"},
  612. dds::fs::current_path() / "_build"};
  613. int run() {
  614. dds::sdist_build_params main_params = {
  615. .subdir = "",
  616. .build_tests = !no_tests.Get(),
  617. .run_tests = !no_tests.Get(),
  618. .build_apps = !no_apps.Get(),
  619. .enable_warnings = !no_warnings.Get(),
  620. };
  621. auto bd = create_project_builder(project.root.Get(),
  622. cat_path.Get(),
  623. repo_path.Get(),
  624. /* load_deps = */ !lm_index,
  625. main_params);
  626. bd.build({
  627. .out_root = out.Get(),
  628. .existing_lm_index = lm_index ? std::make_optional(lm_index.Get()) : std::nullopt,
  629. .emit_lmi = {},
  630. .toolchain = tc_filepath.get_toolchain(),
  631. .parallel_jobs = n_jobs.Get(),
  632. });
  633. return 0;
  634. }
  635. };
  636. /*
  637. ######## ## ## #### ## ######## ######## ######## ######## ######
  638. ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  639. ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  640. ######## ## ## ## ## ## ## ####### ## ## ###### ######## ######
  641. ## ## ## ## ## ## ## ## ## ## ## ## ##
  642. ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  643. ######## ####### #### ######## ######## ######## ######## ## ######
  644. */
  645. struct cli_build_deps {
  646. cli_base& base;
  647. args::Command cmd{base.cmd_group,
  648. "build-deps",
  649. "Build a set of dependencies and emit a libman index"};
  650. toolchain_flag tc{cmd};
  651. repo_path_flag repo_path{cmd};
  652. catalog_path_flag cat_path{cmd};
  653. num_jobs_flag n_jobs{cmd};
  654. args::ValueFlagList<dds::fs::path> deps_files{cmd,
  655. "deps-file",
  656. "Install dependencies from the named files",
  657. {"deps", 'd'}};
  658. path_flag out_path{cmd,
  659. "out-path",
  660. "Directory where build results should be placed",
  661. {"out", 'o'},
  662. dds::fs::current_path() / "_deps"};
  663. path_flag lmi_path{cmd,
  664. "lmi-path",
  665. "Path to the output libman index file (INDEX.lmi)",
  666. {"lmi-path"},
  667. dds::fs::current_path() / "INDEX.lmi"};
  668. args::PositionalList<std::string> deps{cmd, "deps", "List of dependencies to install"};
  669. int run() {
  670. dds::build_params params;
  671. params.out_root = out_path.Get();
  672. params.toolchain = tc.get_toolchain();
  673. params.parallel_jobs = n_jobs.Get();
  674. params.emit_lmi = lmi_path.Get();
  675. dds::builder bd;
  676. dds::sdist_build_params sdist_params;
  677. auto all_file_deps = deps_files.Get() //
  678. | ranges::views::transform([&](auto dep_fpath) {
  679. dds_log(info, "Reading deps from {}", dep_fpath.string());
  680. return dds::dependency_manifest::from_file(dep_fpath).dependencies;
  681. })
  682. | ranges::actions::join;
  683. auto cmd_deps = ranges::views::transform(deps.Get(), [&](auto dep_str) {
  684. return dds::dependency::parse_depends_string(dep_str);
  685. });
  686. auto all_deps = ranges::views::concat(all_file_deps, cmd_deps) | ranges::to_vector;
  687. auto cat = cat_path.open();
  688. dds::repository::with_repository( //
  689. repo_path.Get(),
  690. dds::repo_flags::write_lock | dds::repo_flags::create_if_absent,
  691. [&](dds::repository repo) {
  692. // Download dependencies
  693. dds_log(info, "Loading {} dependencies", all_deps.size());
  694. auto deps = repo.solve(all_deps, cat);
  695. dds::get_all(deps, repo, cat);
  696. for (const dds::package_id& pk : deps) {
  697. auto sdist_ptr = repo.find(pk);
  698. assert(sdist_ptr);
  699. dds::sdist_build_params deps_params;
  700. deps_params.subdir = sdist_ptr->manifest.pkg_id.to_string();
  701. dds_log(info, "Dependency: {}", sdist_ptr->manifest.pkg_id.to_string());
  702. bd.add(*sdist_ptr, deps_params);
  703. }
  704. });
  705. bd.build(params);
  706. return 0;
  707. }
  708. };
  709. } // namespace
  710. /*
  711. ## ## ### #### ## ##
  712. ### ### ## ## ## ### ##
  713. #### #### ## ## ## #### ##
  714. ## ### ## ## ## ## ## ## ##
  715. ## ## ######### ## ## ####
  716. ## ## ## ## ## ## ###
  717. ## ## ## ## #### ## ##
  718. */
  719. int main(int argc, char** argv) {
  720. spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v");
  721. args::ArgumentParser parser("DDS - The drop-dead-simple library manager");
  722. cli_base cli{parser};
  723. cli_compile_file compile_file{cli};
  724. cli_build build{cli};
  725. cli_sdist sdist{cli};
  726. cli_repo repo{cli};
  727. cli_catalog catalog{cli};
  728. cli_build_deps build_deps{cli};
  729. try {
  730. parser.ParseCLI(argc, argv);
  731. } catch (const args::Help&) {
  732. std::cout << parser;
  733. return 0;
  734. } catch (const args::Error& e) {
  735. std::cerr << parser;
  736. std::cerr << e.what() << '\n';
  737. return 1;
  738. }
  739. dds::install_signal_handlers();
  740. dds::log::current_log_level = cli.log_level.Get();
  741. try {
  742. if (cli._verify_ident) {
  743. std::cout << "yes\n";
  744. return 0;
  745. } else if (compile_file.cmd) {
  746. return compile_file.run();
  747. } else if (build.cmd) {
  748. return build.run();
  749. } else if (sdist.cmd) {
  750. return sdist.run();
  751. } else if (repo.cmd) {
  752. return repo.run();
  753. } else if (catalog.cmd) {
  754. return catalog.run();
  755. } else if (build_deps.cmd) {
  756. return build_deps.run();
  757. } else {
  758. assert(false);
  759. std::terminate();
  760. }
  761. } catch (const dds::user_cancelled&) {
  762. dds_log(critical, "Operation cancelled by user");
  763. return 2;
  764. } catch (const dds::error_base& e) {
  765. dds_log(error, "{}", e.what());
  766. dds_log(error, "{}", e.explanation());
  767. dds_log(error, "Refer: {}", e.error_reference());
  768. return 1;
  769. } catch (const std::exception& e) {
  770. dds_log(critical, e.what());
  771. return 2;
  772. }
  773. }