No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

1289 líneas
46KB

  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/http/session.hpp>
  7. #include <dds/remote/remote.hpp>
  8. #include <dds/repo/repo.hpp>
  9. #include <dds/repoman/repoman.hpp>
  10. #include <dds/source/dist.hpp>
  11. #include <dds/toolchain/from_json.hpp>
  12. #include <dds/util/fs.hpp>
  13. #include <dds/util/log.hpp>
  14. #include <dds/util/paths.hpp>
  15. #include <dds/util/result.hpp>
  16. #include <dds/util/signal.hpp>
  17. #include <boost/leaf/handle_error.hpp>
  18. #include <boost/leaf/handle_exception.hpp>
  19. #include <fmt/ostream.h>
  20. #include <json5/parse_data.hpp>
  21. #include <neo/assert.hpp>
  22. #include <neo/sqlite3/error.hpp>
  23. #include <nlohmann/json.hpp>
  24. #include <range/v3/action/join.hpp>
  25. #include <range/v3/range/conversion.hpp>
  26. #include <range/v3/view/concat.hpp>
  27. #include <range/v3/view/group_by.hpp>
  28. #include <range/v3/view/transform.hpp>
  29. #include <spdlog/spdlog.h>
  30. #include <dds/3rd/args.hxx>
  31. #include <filesystem>
  32. #include <iostream>
  33. #include <locale.h>
  34. #include <sstream>
  35. namespace {
  36. using string_flag = args::ValueFlag<std::string>;
  37. using path_flag = args::ValueFlag<dds::fs::path>;
  38. struct toolchain_flag : string_flag {
  39. toolchain_flag(args::Group& grp)
  40. : string_flag{grp,
  41. "toolchain_file",
  42. "Path/identifier of the toolchain to use",
  43. {"toolchain", 't'}} {}
  44. dds::toolchain get_toolchain() {
  45. if (*this) {
  46. return get_arg();
  47. } else {
  48. auto found = dds::toolchain::get_default();
  49. if (!found) {
  50. dds::throw_user_error<dds::errc::no_default_toolchain>();
  51. }
  52. return *found;
  53. }
  54. }
  55. dds::toolchain get_arg() {
  56. const auto tc_path = this->Get();
  57. if (tc_path.find(":") == 0) {
  58. auto default_tc = tc_path.substr(1);
  59. auto tc = dds::toolchain::get_builtin(default_tc);
  60. if (!tc.has_value()) {
  61. dds::throw_user_error<
  62. dds::errc::invalid_builtin_toolchain>("Invalid built-in toolchain name '{}'",
  63. default_tc);
  64. }
  65. return std::move(*tc);
  66. } else {
  67. return dds::parse_toolchain_json5(dds::slurp_file(tc_path));
  68. // return dds::parse_toolchain_dds(dds::slurp_file(tc_path));
  69. }
  70. }
  71. };
  72. struct repo_path_flag : path_flag {
  73. repo_path_flag(args::Group& grp)
  74. : path_flag{grp,
  75. "dir",
  76. "Path to the DDS repository directory",
  77. {"repo-dir"},
  78. dds::repository::default_local_path()} {}
  79. };
  80. struct catalog_path_flag : path_flag {
  81. catalog_path_flag(args::Group& cmd)
  82. : path_flag(cmd,
  83. "catalog-path",
  84. "Override the path to the catalog database",
  85. {"catalog", 'c'},
  86. dds::dds_data_dir() / "catalog.db") {}
  87. dds::catalog open() { return dds::catalog::open(Get()); }
  88. };
  89. struct num_jobs_flag : args::ValueFlag<int> {
  90. num_jobs_flag(args::Group& cmd)
  91. : ValueFlag(cmd,
  92. "jobs",
  93. "Set the number of parallel jobs when compiling files",
  94. {"jobs", 'j'},
  95. 0) {}
  96. };
  97. /**
  98. * Base class holds the actual argument parser
  99. */
  100. struct cli_base {
  101. args::ArgumentParser& parser;
  102. args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}};
  103. // Test argument:
  104. args::Flag _verify_ident{parser,
  105. "test",
  106. "Print `yes` and exit 0. Useful for scripting.",
  107. {"are-you-the-real-dds?"}};
  108. args::MapFlag<std::string, dds::log::level> log_level{
  109. parser,
  110. "log-level",
  111. "Set the logging level",
  112. {"log-level", 'l'},
  113. {
  114. {"trace", dds::log::level::trace},
  115. {"debug", dds::log::level::debug},
  116. {"info", dds::log::level::info},
  117. {"warn", dds::log::level::warn},
  118. {"error", dds::log::level::error},
  119. {"critical", dds::log::level::critical},
  120. },
  121. dds::log::level::info,
  122. };
  123. args::Group cmd_group{parser, "Available Commands"};
  124. };
  125. /**
  126. * Flags common to all subcommands
  127. */
  128. struct common_flags {
  129. args::Command& cmd;
  130. args::HelpFlag _help{cmd, "help", "Print this help message and exit", {'h', "help"}};
  131. };
  132. /**
  133. * Flags common to project-related commands
  134. */
  135. struct common_project_flags {
  136. args::Command& cmd;
  137. path_flag root{cmd,
  138. "project_dir",
  139. "Path to the directory containing the project",
  140. {'p', "project-dir"},
  141. dds::fs::current_path()};
  142. };
  143. /*
  144. ###### ### ######## ### ## ####### ######
  145. ## ## ## ## ## ## ## ## ## ## ## ##
  146. ## ## ## ## ## ## ## ## ## ##
  147. ## ## ## ## ## ## ## ## ## ## ####
  148. ## ######### ## ######### ## ## ## ## ##
  149. ## ## ## ## ## ## ## ## ## ## ## ##
  150. ###### ## ## ## ## ## ######## ####### ######
  151. */
  152. struct cli_catalog {
  153. cli_base& base;
  154. args::Command cmd{base.cmd_group, "catalog", "Manage the package catalog"};
  155. common_flags _common{cmd};
  156. args::Group cat_group{cmd, "Catalog subcommands"};
  157. struct {
  158. cli_catalog& parent;
  159. args::Command cmd{parent.cat_group, "create", "Create a catalog database"};
  160. common_flags _common{cmd};
  161. catalog_path_flag cat_path{cmd};
  162. int run() {
  163. // Simply opening the DB will initialize the catalog
  164. cat_path.open();
  165. return 0;
  166. }
  167. } create{*this};
  168. struct {
  169. cli_catalog& parent;
  170. args::Command cmd{parent.cat_group, "import", "Import entries into a catalog"};
  171. common_flags _common{cmd};
  172. catalog_path_flag cat_path{cmd};
  173. args::Flag import_stdin{cmd, "stdin", "Import JSON from stdin", {"stdin"}};
  174. args::Flag init{cmd, "initial", "Re-import the initial catalog contents", {"initial"}};
  175. args::ValueFlagList<std::string>
  176. json_paths{cmd,
  177. "json",
  178. "Import catalog entries from the given JSON files",
  179. {"json", 'j'}};
  180. int run() {
  181. auto cat = cat_path.open();
  182. if (init.Get()) {
  183. cat.import_initial();
  184. }
  185. for (const auto& json_fpath : json_paths.Get()) {
  186. cat.import_json_file(json_fpath);
  187. }
  188. if (import_stdin.Get()) {
  189. std::ostringstream strm;
  190. strm << std::cin.rdbuf();
  191. cat.import_json_str(strm.str());
  192. }
  193. return 0;
  194. }
  195. } import{*this};
  196. struct {
  197. cli_catalog& parent;
  198. args::Command cmd{parent.cat_group, "get", "Obtain an sdist from a catalog listing"};
  199. common_flags _common{cmd};
  200. catalog_path_flag cat_path{cmd};
  201. path_flag out{cmd,
  202. "out",
  203. "The directory where the source distributions will be placed",
  204. {"out-dir", 'o'},
  205. dds::fs::current_path()};
  206. args::PositionalList<std::string> requirements{cmd,
  207. "requirement",
  208. "The package IDs to obtain"};
  209. int run() {
  210. auto cat = cat_path.open();
  211. for (const auto& req : requirements.Get()) {
  212. auto id = dds::package_id::parse(req);
  213. dds::dym_target dym;
  214. auto info = cat.get(id);
  215. if (!info) {
  216. dds::throw_user_error<dds::errc::no_such_catalog_package>(
  217. "No package in the catalog matched the ID '{}'.{}",
  218. req,
  219. dym.sentence_suffix());
  220. }
  221. auto tsd = dds::get_package_sdist(*info);
  222. auto out_path = out.Get();
  223. auto dest = out_path / id.to_string();
  224. dds_log(info, "Create sdist at {}", dest.string());
  225. dds::fs::remove_all(dest);
  226. dds::safe_rename(tsd.sdist.path, dest);
  227. }
  228. return 0;
  229. }
  230. } get{*this};
  231. struct {
  232. cli_catalog& parent;
  233. args::Command cmd{parent.cat_group, "add", "Manually add an entry to the catalog database"};
  234. common_flags _common{cmd};
  235. catalog_path_flag cat_path{cmd};
  236. args::Positional<std::string> pkg_id{cmd,
  237. "<id>",
  238. "The name@version ID of the package to add",
  239. args::Options::Required};
  240. args::Positional<std::string> uri{cmd,
  241. "<uri>",
  242. "The URI of the package",
  243. args::Options::Required};
  244. // string_flag auto_lib{cmd,
  245. // "auto-lib",
  246. // "Set the auto-library information for this package",
  247. // {"auto-lib"}};
  248. args::ValueFlagList<std::string> deps{cmd,
  249. "depends",
  250. "The dependencies of this package",
  251. {"depends", 'd'}};
  252. string_flag description{cmd, "description", "A description of the package", {"desc"}};
  253. int run() {
  254. auto ident = dds::package_id::parse(pkg_id.Get());
  255. std::vector<dds::dependency> deps;
  256. for (const auto& dep : this->deps.Get()) {
  257. auto dep_id = dds::package_id::parse(dep);
  258. assert(false && "TODO");
  259. // deps.push_back({dep_id.name, dep_id.version});
  260. }
  261. auto remote = dds::parse_remote_url(uri.Get());
  262. neo_assertion_breadcrumbs("Running 'catalog add'",
  263. uri.Get(),
  264. description.Get(),
  265. pkg_id.Get());
  266. dds::package_info info{ident, std::move(deps), description.Get(), remote};
  267. cat_path.open().store(info);
  268. return 0;
  269. }
  270. } add{*this};
  271. struct {
  272. cli_catalog& parent;
  273. args::Command cmd{parent.cat_group, "list", "List the contents of the catalog"};
  274. catalog_path_flag cat_path{cmd};
  275. string_flag name{cmd, "name", "Only list packages with the given name", {"name", 'n'}};
  276. int run() {
  277. auto cat = cat_path.open();
  278. auto pkgs = name ? cat.by_name(name.Get()) : cat.all();
  279. for (const dds::package_id& pk : pkgs) {
  280. std::cout << pk.to_string() << '\n';
  281. }
  282. return 0;
  283. }
  284. } list{*this};
  285. struct {
  286. cli_catalog& parent;
  287. args::Command cmd{parent.cat_group,
  288. "show",
  289. "Show information about a single package in the catalog"};
  290. catalog_path_flag cat_path{cmd};
  291. args::Positional<std::string> ident{cmd,
  292. "package-id",
  293. "A package identifier to show",
  294. args::Options::Required};
  295. void print_remote_info(const dds::git_remote_listing& git) {
  296. std::cout << "Git URL: " << git.url << '\n';
  297. std::cout << "Git Ref: " << git.ref << '\n';
  298. if (git.auto_lib) {
  299. std::cout << "Auto-lib: " << git.auto_lib->name << "/" << git.auto_lib->namespace_
  300. << '\n';
  301. }
  302. }
  303. void print_remote_info(const dds::http_remote_listing& http) {
  304. fmt::print("HTTP/S URL: {}", http.url);
  305. if (http.auto_lib) {
  306. fmt::print("Auto-lib: {}/{}", http.auto_lib->name, http.auto_lib->namespace_);
  307. }
  308. }
  309. void print_remote_info(std::monostate) {
  310. std::cout << "THIS ENTRY IS MISSING REMOTE INFORMATION!\n";
  311. }
  312. int run() {
  313. auto pk_id = dds::package_id::parse(ident.Get());
  314. auto cat = cat_path.open();
  315. auto pkg = cat.get(pk_id);
  316. if (!pkg) {
  317. dds_log(error, "No package '{}' in the catalog", pk_id.to_string());
  318. return 1;
  319. }
  320. std::cout << "Name: " << pkg->ident.name << '\n'
  321. << "Version: " << pkg->ident.version << '\n';
  322. for (const auto& dep : pkg->deps) {
  323. std::cout << "Depends: " << dep.to_string() << '\n';
  324. }
  325. std::visit([&](const auto& remote) { print_remote_info(remote); }, pkg->remote);
  326. std::cout << "Description:\n " << pkg->description << '\n';
  327. return 0;
  328. }
  329. } show{*this};
  330. int run() {
  331. if (create.cmd) {
  332. return create.run();
  333. } else if (import.cmd) {
  334. return import.run();
  335. } else if (get.cmd) {
  336. return get.run();
  337. } else if (add.cmd) {
  338. return add.run();
  339. } else if (list.cmd) {
  340. return list.run();
  341. } else if (show.cmd) {
  342. return show.run();
  343. } else {
  344. assert(false);
  345. std::terminate();
  346. }
  347. }
  348. };
  349. /*
  350. ######## ######## ######## ####### ## ## ### ## ##
  351. ## ## ## ## ## ## ## ### ### ## ## ### ##
  352. ## ## ## ## ## ## ## #### #### ## ## #### ##
  353. ######## ###### ######## ## ## ## ### ## ## ## ## ## ##
  354. ## ## ## ## ## ## ## ## ######### ## ####
  355. ## ## ## ## ## ## ## ## ## ## ## ###
  356. ## ## ######## ## ####### ## ## ## ## ## ##
  357. */
  358. struct cli_repoman {
  359. cli_base& base;
  360. args::Command cmd{base.cmd_group, "repoman", "Manage a package package repository"};
  361. common_flags _common{cmd};
  362. args::Group repoman_group{cmd, "Repoman subcommand"};
  363. struct {
  364. cli_repoman& parent;
  365. args::Command cmd{parent.repoman_group, "init", "Initialize a new repository directory"};
  366. common_flags _common{cmd};
  367. args::Positional<dds::fs::path> where{cmd,
  368. "<repo-path>",
  369. "Directory where the repository will be created",
  370. args::Options::Required};
  371. string_flag name{cmd,
  372. "<name>",
  373. "Give the repository a name (should be GLOBALLY unique). If not provided, "
  374. "a new random one will be generated.",
  375. {"name"}};
  376. int run() {
  377. auto repo
  378. = dds::repo_manager::create(where.Get(),
  379. name ? std::make_optional(name.Get()) : std::nullopt);
  380. dds_log(info, "Created new repository '{}' in {}", repo.root(), repo.name());
  381. return 0;
  382. }
  383. } init{*this};
  384. struct {
  385. cli_repoman& parent;
  386. args::Command cmd{parent.repoman_group, "import", "Import packages into a repository"};
  387. common_flags _common{cmd};
  388. args::Positional<dds::fs::path> where{cmd,
  389. "<repo-path>",
  390. "Directory of the repository to import",
  391. args::Options::Required};
  392. args::PositionalList<dds::fs::path> files{cmd,
  393. "<targz-path>",
  394. "Path to one or more sdist archives to import"};
  395. int run() {
  396. auto repo = dds::repo_manager::open(where.Get());
  397. for (auto pkg : files.Get()) {
  398. repo.import_targz(pkg);
  399. }
  400. return 0;
  401. }
  402. } import{*this};
  403. struct {
  404. cli_repoman& parent;
  405. args::Command cmd{parent.repoman_group, "remove", "Remove packages from the repository"};
  406. common_flags _common{cmd};
  407. args::Positional<dds::fs::path> where{cmd,
  408. "<repo-path>",
  409. "Directory of the repository to import",
  410. args::Options::Required};
  411. args::PositionalList<std::string> packages{cmd,
  412. "<package-id>",
  413. "One or more identifiers of packages to remove"};
  414. int run() {
  415. auto repo = dds::repo_manager::open(where.Get());
  416. for (auto& str : packages) {
  417. auto pkg_id = dds::package_id::parse(str);
  418. repo.delete_package(pkg_id);
  419. }
  420. return 0;
  421. }
  422. } remove{*this};
  423. struct {
  424. cli_repoman& parent;
  425. args::Command cmd{parent.repoman_group, "ls", "List packages in the repository"};
  426. common_flags _common{cmd};
  427. args::Positional<dds::fs::path> where{cmd,
  428. "<repo-path>",
  429. "Directory of the repository to inspect",
  430. args::Options::Required};
  431. int run() {
  432. auto repo = dds::repo_manager::open(where.Get());
  433. for (auto pkg_id : repo.all_packages()) {
  434. std::cout << pkg_id.to_string() << '\n';
  435. }
  436. return 0;
  437. }
  438. } ls{*this};
  439. dds::result<int> _run() {
  440. if (init.cmd) {
  441. return init.run();
  442. } else if (import.cmd) {
  443. return import.run();
  444. } else if (remove.cmd) {
  445. return remove.run();
  446. } else if (ls.cmd) {
  447. return ls.run();
  448. }
  449. return 66;
  450. }
  451. int run() {
  452. return boost::leaf::try_handle_all( //
  453. [&]() -> dds::result<int> {
  454. try {
  455. return _run();
  456. } catch (...) {
  457. return dds::capture_exception();
  458. }
  459. },
  460. [](dds::e_sqlite3_error_exc,
  461. boost::leaf::match<neo::sqlite3::errc, neo::sqlite3::errc::constraint_unique>,
  462. dds::e_repo_import_targz tgz,
  463. dds::package_id pkg_id) {
  464. dds_log(error,
  465. "Package {} (from {}) is already present in the repository",
  466. pkg_id.to_string(),
  467. tgz.path);
  468. return 1;
  469. },
  470. [](dds::e_sqlite3_error_exc e, dds::e_repo_import_targz tgz) {
  471. dds_log(error,
  472. "Database error while importing tar file {}: {}",
  473. tgz.path,
  474. e.message);
  475. return 1;
  476. },
  477. [](dds::e_sqlite3_error_exc e, dds::e_init_repo init, dds::e_init_repo_db init_db) {
  478. dds_log(
  479. error,
  480. "SQLite error while initializing repository in [{}] (SQlite database {}): {}",
  481. init.path,
  482. init_db.path,
  483. e.message);
  484. return 1;
  485. },
  486. [](dds::e_system_error_exc e, dds::e_repo_import_targz tgz) {
  487. dds_log(error, "Failed to import package archive {}: {}", tgz.path, e.message);
  488. return 1;
  489. },
  490. [](dds::e_system_error_exc e, dds::e_open_repo_db db) {
  491. dds_log(error,
  492. "Error while opening repository database {}: {}",
  493. db.path,
  494. e.message);
  495. return 1;
  496. },
  497. [](dds::e_sqlite3_error_exc e, dds::e_init_repo init) {
  498. dds_log(error,
  499. "SQLite error while initializing repository in [{}]: {}",
  500. init.path,
  501. e.message);
  502. return 1;
  503. },
  504. [](dds::e_system_error_exc e, dds::e_repo_delete_targz tgz, dds::package_id pkg_id) {
  505. dds_log(error,
  506. "Cannot delete requested package '{}' from repository (Archive {}): {}",
  507. pkg_id.to_string(),
  508. tgz.path,
  509. e.message);
  510. return 1;
  511. },
  512. [](dds::e_system_error_exc e) {
  513. dds_log(error, "Unhandled system_error: {}", e.message);
  514. return 1;
  515. },
  516. [](boost::leaf::diagnostic_info const& info) {
  517. dds_log(error, "Unknown error: {}", info);
  518. return 42;
  519. });
  520. }
  521. };
  522. /*
  523. ######## ######## ######## #######
  524. ## ## ## ## ## ## ##
  525. ## ## ## ## ## ## ##
  526. ######## ###### ######## ## ##
  527. ## ## ## ## ## ##
  528. ## ## ## ## ## ##
  529. ## ## ######## ## #######
  530. */
  531. struct cli_repo {
  532. cli_base& base;
  533. args::Command cmd{base.cmd_group, "repo", "Manage the package repository"};
  534. common_flags _common{cmd};
  535. repo_path_flag where{cmd};
  536. args::Group repo_group{cmd, "Repo subcommands"};
  537. struct {
  538. cli_repo& parent;
  539. args::Command cmd{parent.repo_group, "ls", "List repository contents"};
  540. common_flags _common{cmd};
  541. int run() {
  542. auto list_contents = [&](dds::repository repo) {
  543. auto same_name = [](auto&& a, auto&& b) {
  544. return a.manifest.pkg_id.name == b.manifest.pkg_id.name;
  545. };
  546. auto all = repo.iter_sdists();
  547. auto grp_by_name = all //
  548. | ranges::views::group_by(same_name) //
  549. | ranges::views::transform(ranges::to_vector) //
  550. | ranges::views::transform([](auto&& grp) {
  551. assert(grp.size() > 0);
  552. return std::pair(grp[0].manifest.pkg_id.name, grp);
  553. });
  554. for (const auto& [name, grp] : grp_by_name) {
  555. dds_log(info, "{}:", name);
  556. for (const dds::sdist& sd : grp) {
  557. dds_log(info, " - {}", sd.manifest.pkg_id.version.to_string());
  558. }
  559. }
  560. return 0;
  561. };
  562. return dds::repository::with_repository(parent.where.Get(),
  563. dds::repo_flags::read,
  564. list_contents);
  565. }
  566. } ls{*this};
  567. struct {
  568. cli_repo& parent;
  569. args::Command cmd{parent.repo_group,
  570. "import",
  571. "Import a source distribution archive file into the repository"};
  572. common_flags _common{cmd};
  573. args::PositionalList<std::string>
  574. sdist_paths{cmd,
  575. "sdist-path-or-url",
  576. "Path/URL to one or more source distribution archives"};
  577. args::Flag force{cmd,
  578. "replace-if-exists",
  579. "Replace an existing package in the repository",
  580. {"replace"}};
  581. args::Flag import_stdin{cmd,
  582. "import-stdin",
  583. "Import a source distribution tarball from stdin",
  584. {"stdin"}};
  585. int run() {
  586. auto import_sdists = [&](dds::repository repo) {
  587. auto if_exists_action
  588. = force.Get() ? dds::if_exists::replace : dds::if_exists::throw_exc;
  589. for (std::string_view tgz_where : sdist_paths.Get()) {
  590. auto tmp_sd
  591. = (tgz_where.starts_with("http://") || tgz_where.starts_with("https://"))
  592. ? dds::download_expand_sdist_targz(tgz_where)
  593. : dds::expand_sdist_targz(tgz_where);
  594. repo.add_sdist(tmp_sd.sdist, if_exists_action);
  595. }
  596. if (import_stdin) {
  597. auto tmp_sd = dds::expand_sdist_from_istream(std::cin, "<stdin>");
  598. repo.add_sdist(tmp_sd.sdist, if_exists_action);
  599. }
  600. return 0;
  601. };
  602. return dds::repository::with_repository(parent.where.Get(),
  603. dds::repo_flags::write_lock
  604. | dds::repo_flags::create_if_absent,
  605. import_sdists);
  606. }
  607. } import_{*this};
  608. struct {
  609. cli_repo& parent;
  610. args::Command cmd{parent.repo_group, "add", "Add a remote repository"};
  611. common_flags _flags{cmd};
  612. catalog_path_flag cat_path{cmd};
  613. args::Positional<std::string> url{cmd,
  614. "<url>",
  615. "URL of a repository to add",
  616. args::Options::Required};
  617. args::Flag update{cmd, "update", "Update catalog contents immediately", {"update", 'U'}};
  618. int run() {
  619. return boost::leaf::try_handle_all( //
  620. [&]() -> dds::result<int> {
  621. try {
  622. auto cat = cat_path.open();
  623. auto repo = dds::remote_repository::connect(url.Get());
  624. repo.store(cat.database());
  625. if (update) {
  626. repo.update_catalog(cat.database());
  627. }
  628. } catch (...) {
  629. return dds::capture_exception();
  630. }
  631. return 0;
  632. },
  633. [&](neo::url_validation_error url_err, dds::e_url_string bad_url) {
  634. dds_log(error, "Invalid URL [{}]: {}", bad_url.value, url_err.what());
  635. return 1;
  636. },
  637. [&](const json5::parse_error& e, dds::e_http_url bad_url) {
  638. dds_log(error,
  639. "Error parsing JSON downloaded from URL [{}]: {}",
  640. bad_url.value,
  641. e.what());
  642. return 1;
  643. },
  644. [](dds::e_sqlite3_error_exc e, dds::e_url_string url) {
  645. dds_log(error,
  646. "Error accessing remote database (From {}): {}",
  647. url.value,
  648. e.message);
  649. return 1;
  650. },
  651. [](dds::e_sqlite3_error_exc e) {
  652. dds_log(error, "Unexpected database error: {}", e.message);
  653. return 1;
  654. },
  655. [&](dds::e_system_error_exc e, dds::e_http_connect conn) {
  656. dds_log(error,
  657. "Error opening connection to [{}:{}]: {}",
  658. conn.host,
  659. conn.port,
  660. e.message);
  661. return 1;
  662. },
  663. [](const std::exception& e) {
  664. dds_log(error, "An unknown unhandled exception occurred: {}", e.what());
  665. return 1;
  666. },
  667. [](dds::e_system_error_exc e) {
  668. dds_log(error, "An unknown system_error occurred: {}", e.message);
  669. return 42;
  670. },
  671. [](boost::leaf::diagnostic_info const& info) {
  672. dds_log(error, "An unnknown error occurred? {}", info);
  673. return 42;
  674. });
  675. }
  676. } add{*this};
  677. struct {
  678. cli_repo& parent;
  679. args::Command cmd{parent.repo_group, "init", "Initialize a directory as a repository"};
  680. common_flags _common{cmd};
  681. int run() {
  682. if (parent.where.Get().empty()) {
  683. throw args::ParseError("The --dir flag is required");
  684. }
  685. auto repo_dir = dds::fs::absolute(parent.where.Get());
  686. dds::repository::with_repository(repo_dir, dds::repo_flags::create_if_absent, [](auto) {
  687. });
  688. return 0;
  689. }
  690. } init{*this};
  691. int run() {
  692. if (ls.cmd) {
  693. return ls.run();
  694. } else if (init.cmd) {
  695. return init.run();
  696. } else if (import_.cmd) {
  697. return import_.run();
  698. } else if (add.cmd) {
  699. return add.run();
  700. } else {
  701. assert(false);
  702. std::terminate();
  703. }
  704. }
  705. };
  706. /*
  707. ###### ######## #### ###### ########
  708. ## ## ## ## ## ## ## ##
  709. ## ## ## ## ## ##
  710. ###### ## ## ## ###### ##
  711. ## ## ## ## ## ##
  712. ## ## ## ## ## ## ## ##
  713. ###### ######## #### ###### ##
  714. */
  715. struct cli_sdist {
  716. cli_base& base;
  717. args::Command cmd{base.cmd_group, "sdist", "Work with source distributions"};
  718. common_flags _common{cmd};
  719. args::Group sdist_group{cmd, "`sdist` commands"};
  720. struct {
  721. cli_sdist& parent;
  722. args::Command cmd{parent.sdist_group, "create", "Create a source distribution"};
  723. common_project_flags project{cmd};
  724. path_flag out{cmd, "out", "The destination of the source distribution", {"out"}};
  725. args::Flag force{cmd,
  726. "replace-if-exists",
  727. "Forcibly replace an existing distribution",
  728. {"replace"}};
  729. int run() {
  730. dds::sdist_params params;
  731. params.project_dir = project.root.Get();
  732. params.dest_path = out.Get();
  733. params.force = force.Get();
  734. params.include_apps = true;
  735. params.include_tests = true;
  736. auto pkg_man = dds::package_manifest::load_from_directory(project.root.Get());
  737. if (!pkg_man) {
  738. dds::throw_user_error<dds::errc::invalid_pkg_manifest>(
  739. "Creating a source distribution requires a package manifest");
  740. }
  741. std::string default_filename = fmt::format("{}@{}.tar.gz",
  742. pkg_man->pkg_id.name,
  743. pkg_man->pkg_id.version.to_string());
  744. auto default_filepath = dds::fs::current_path() / default_filename;
  745. auto out_path = out.Matched() ? out.Get() : default_filepath;
  746. dds::create_sdist_targz(out_path, params);
  747. dds_log(info, "Generate source distribution at [{}]", out_path.string());
  748. return 0;
  749. }
  750. } create{*this};
  751. struct {
  752. cli_sdist& parent;
  753. args::Command cmd{parent.sdist_group,
  754. "export",
  755. "Export a source distribution to a repository"};
  756. common_project_flags project{cmd};
  757. repo_path_flag repo_where{cmd};
  758. args::Flag force{cmd,
  759. "replace-if-exists",
  760. "Replace an existing export in the repository",
  761. {"replace"}};
  762. int run() {
  763. auto repo_dir = repo_where.Get();
  764. // TODO: Generate a unique name to avoid conflicts
  765. auto tmp_sdist = dds::fs::temp_directory_path() / ".dds-sdist";
  766. if (dds::fs::exists(tmp_sdist)) {
  767. dds::fs::remove_all(tmp_sdist);
  768. }
  769. dds::sdist_params params;
  770. params.project_dir = project.root.Get();
  771. params.dest_path = tmp_sdist;
  772. params.force = true;
  773. auto sdist = dds::create_sdist(params);
  774. dds::repository::with_repository( //
  775. repo_dir,
  776. dds::repo_flags::create_if_absent | dds::repo_flags::write_lock,
  777. [&](dds::repository repo) { //
  778. repo.add_sdist(sdist,
  779. force.Get() ? dds::if_exists::replace
  780. : dds::if_exists::throw_exc);
  781. });
  782. return 0;
  783. }
  784. } export_{*this};
  785. int run() {
  786. if (create.cmd) {
  787. return create.run();
  788. } else if (export_.cmd) {
  789. return export_.run();
  790. } else {
  791. assert(false && "Unreachable");
  792. std::terminate();
  793. }
  794. }
  795. };
  796. void load_project_deps(dds::builder& bd,
  797. const dds::package_manifest& man,
  798. dds::path_ref cat_path,
  799. dds::path_ref repo_path) {
  800. auto cat = dds::catalog::open(cat_path);
  801. // Build the dependencies
  802. dds::repository::with_repository( //
  803. repo_path,
  804. dds::repo_flags::write_lock | dds::repo_flags::create_if_absent,
  805. [&](dds::repository repo) {
  806. // Download dependencies
  807. auto deps = repo.solve(man.dependencies, cat);
  808. dds::get_all(deps, repo, cat);
  809. for (const dds::package_id& pk : deps) {
  810. auto sdist_ptr = repo.find(pk);
  811. assert(sdist_ptr);
  812. dds::sdist_build_params deps_params;
  813. deps_params.subdir
  814. = dds::fs::path("_deps") / sdist_ptr->manifest.pkg_id.to_string();
  815. bd.add(*sdist_ptr, deps_params);
  816. }
  817. });
  818. }
  819. dds::builder create_project_builder(dds::path_ref pr_dir,
  820. dds::path_ref cat_path,
  821. dds::path_ref repo_path,
  822. bool load_deps,
  823. const dds::sdist_build_params& project_params) {
  824. auto man = dds::package_manifest::load_from_directory(pr_dir).value_or(dds::package_manifest{});
  825. dds::builder builder;
  826. if (load_deps) {
  827. load_project_deps(builder, man, cat_path, repo_path);
  828. }
  829. builder.add(dds::sdist{std::move(man), pr_dir}, project_params);
  830. return builder;
  831. }
  832. /*
  833. ###### ####### ## ## ######## #### ## ########
  834. ## ## ## ## ### ### ## ## ## ## ##
  835. ## ## ## #### #### ## ## ## ## ##
  836. ## ## ## ## ### ## ######## ## ## ######
  837. ## ## ## ## ## ## ## ## ##
  838. ## ## ## ## ## ## ## ## ## ##
  839. ###### ####### ## ## ## #### ######## ########
  840. */
  841. struct cli_compile_file {
  842. cli_base& base;
  843. args::Command cmd{base.cmd_group, "compile-file", "Compile a single file"};
  844. common_flags _flags{cmd};
  845. common_project_flags project{cmd};
  846. catalog_path_flag cat_path{cmd};
  847. repo_path_flag repo_path{cmd};
  848. args::Flag no_warnings{cmd, "no-warnings", "Disable compiler warnings", {"no-warnings"}};
  849. toolchain_flag tc_filepath{cmd};
  850. path_flag
  851. lm_index{cmd,
  852. "lm_index",
  853. "Path to an existing libman index from which to load deps (usually INDEX.lmi)",
  854. {"lm-index", 'I'}};
  855. num_jobs_flag n_jobs{cmd};
  856. path_flag out{cmd,
  857. "out",
  858. "The root build directory",
  859. {"out"},
  860. dds::fs::current_path() / "_build"};
  861. args::PositionalList<dds::fs::path> source_files{cmd,
  862. "source-files",
  863. "One or more source files to compile"};
  864. int run() {
  865. dds::sdist_build_params main_params = {
  866. .subdir = "",
  867. .build_tests = true,
  868. .build_apps = true,
  869. .enable_warnings = !no_warnings.Get(),
  870. };
  871. auto bd = create_project_builder(project.root.Get(),
  872. cat_path.Get(),
  873. repo_path.Get(),
  874. /* load_deps = */ !lm_index,
  875. main_params);
  876. bd.compile_files(source_files.Get(),
  877. {
  878. .out_root = out.Get(),
  879. .existing_lm_index
  880. = lm_index ? std::make_optional(lm_index.Get()) : std::nullopt,
  881. .emit_lmi = {},
  882. .toolchain = tc_filepath.get_toolchain(),
  883. .parallel_jobs = n_jobs.Get(),
  884. });
  885. return 0;
  886. }
  887. };
  888. /*
  889. ######## ## ## #### ## ########
  890. ## ## ## ## ## ## ## ##
  891. ## ## ## ## ## ## ## ##
  892. ######## ## ## ## ## ## ##
  893. ## ## ## ## ## ## ## ##
  894. ## ## ## ## ## ## ## ##
  895. ######## ####### #### ######## ########
  896. */
  897. struct cli_build {
  898. cli_base& base;
  899. args::Command cmd{base.cmd_group, "build", "Build a project"};
  900. common_flags _common{cmd};
  901. common_project_flags project{cmd};
  902. catalog_path_flag cat_path{cmd};
  903. repo_path_flag repo_path{cmd};
  904. args::Flag no_tests{cmd, "no-tests", "Do not build and run tests", {"no-tests"}};
  905. args::Flag no_apps{cmd, "no-apps", "Do not compile and link applications", {"no-apps"}};
  906. args::Flag no_warnings{cmd, "no-warings", "Disable build warnings", {"no-warnings"}};
  907. toolchain_flag tc_filepath{cmd};
  908. path_flag
  909. lm_index{cmd,
  910. "lm_index",
  911. "Path to an existing libman index from which to load deps (usually INDEX.lmi)",
  912. {"lm-index", 'I'}};
  913. num_jobs_flag n_jobs{cmd};
  914. path_flag out{cmd,
  915. "out",
  916. "The root build directory",
  917. {"out"},
  918. dds::fs::current_path() / "_build"};
  919. int run() {
  920. dds::sdist_build_params main_params = {
  921. .subdir = "",
  922. .build_tests = !no_tests.Get(),
  923. .run_tests = !no_tests.Get(),
  924. .build_apps = !no_apps.Get(),
  925. .enable_warnings = !no_warnings.Get(),
  926. };
  927. auto bd = create_project_builder(project.root.Get(),
  928. cat_path.Get(),
  929. repo_path.Get(),
  930. /* load_deps = */ !lm_index,
  931. main_params);
  932. bd.build({
  933. .out_root = out.Get(),
  934. .existing_lm_index = lm_index ? std::make_optional(lm_index.Get()) : std::nullopt,
  935. .emit_lmi = {},
  936. .toolchain = tc_filepath.get_toolchain(),
  937. .parallel_jobs = n_jobs.Get(),
  938. });
  939. return 0;
  940. }
  941. };
  942. /*
  943. ######## ## ## #### ## ######## ######## ######## ######## ######
  944. ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  945. ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  946. ######## ## ## ## ## ## ## ####### ## ## ###### ######## ######
  947. ## ## ## ## ## ## ## ## ## ## ## ## ##
  948. ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  949. ######## ####### #### ######## ######## ######## ######## ## ######
  950. */
  951. struct cli_build_deps {
  952. cli_base& base;
  953. args::Command cmd{base.cmd_group,
  954. "build-deps",
  955. "Build a set of dependencies and emit a libman index"};
  956. toolchain_flag tc{cmd};
  957. repo_path_flag repo_path{cmd};
  958. catalog_path_flag cat_path{cmd};
  959. num_jobs_flag n_jobs{cmd};
  960. args::ValueFlagList<dds::fs::path> deps_files{cmd,
  961. "deps-file",
  962. "Install dependencies from the named files",
  963. {"deps", 'd'}};
  964. path_flag out_path{cmd,
  965. "out-path",
  966. "Directory where build results should be placed",
  967. {"out", 'o'},
  968. dds::fs::current_path() / "_deps"};
  969. path_flag lmi_path{cmd,
  970. "lmi-path",
  971. "Path to the output libman index file (INDEX.lmi)",
  972. {"lmi-path"},
  973. dds::fs::current_path() / "INDEX.lmi"};
  974. args::PositionalList<std::string> deps{cmd, "deps", "List of dependencies to install"};
  975. int run() {
  976. dds::build_params params;
  977. params.out_root = out_path.Get();
  978. params.toolchain = tc.get_toolchain();
  979. params.parallel_jobs = n_jobs.Get();
  980. params.emit_lmi = lmi_path.Get();
  981. dds::builder bd;
  982. dds::sdist_build_params sdist_params;
  983. auto all_file_deps = deps_files.Get() //
  984. | ranges::views::transform([&](auto dep_fpath) {
  985. dds_log(info, "Reading deps from {}", dep_fpath.string());
  986. return dds::dependency_manifest::from_file(dep_fpath).dependencies;
  987. })
  988. | ranges::actions::join;
  989. auto cmd_deps = ranges::views::transform(deps.Get(), [&](auto dep_str) {
  990. return dds::dependency::parse_depends_string(dep_str);
  991. });
  992. auto all_deps = ranges::views::concat(all_file_deps, cmd_deps) | ranges::to_vector;
  993. auto cat = cat_path.open();
  994. dds::repository::with_repository( //
  995. repo_path.Get(),
  996. dds::repo_flags::write_lock | dds::repo_flags::create_if_absent,
  997. [&](dds::repository repo) {
  998. // Download dependencies
  999. dds_log(info, "Loading {} dependencies", all_deps.size());
  1000. auto deps = repo.solve(all_deps, cat);
  1001. dds::get_all(deps, repo, cat);
  1002. for (const dds::package_id& pk : deps) {
  1003. auto sdist_ptr = repo.find(pk);
  1004. assert(sdist_ptr);
  1005. dds::sdist_build_params deps_params;
  1006. deps_params.subdir = sdist_ptr->manifest.pkg_id.to_string();
  1007. dds_log(info, "Dependency: {}", sdist_ptr->manifest.pkg_id.to_string());
  1008. bd.add(*sdist_ptr, deps_params);
  1009. }
  1010. });
  1011. bd.build(params);
  1012. return 0;
  1013. }
  1014. };
  1015. } // namespace
  1016. /*
  1017. ## ## ### #### ## ##
  1018. ### ### ## ## ## ### ##
  1019. #### #### ## ## ## #### ##
  1020. ## ### ## ## ## ## ## ## ##
  1021. ## ## ######### ## ## ####
  1022. ## ## ## ## ## ## ###
  1023. ## ## ## ## #### ## ##
  1024. */
  1025. int main_fn(const std::vector<std::string>& argv) {
  1026. spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v");
  1027. args::ArgumentParser parser("DDS - The drop-dead-simple library manager");
  1028. cli_base cli{parser};
  1029. cli_compile_file compile_file{cli};
  1030. cli_build build{cli};
  1031. cli_sdist sdist{cli};
  1032. cli_repo repo{cli};
  1033. cli_repoman repoman{cli};
  1034. cli_catalog catalog{cli};
  1035. cli_build_deps build_deps{cli};
  1036. try {
  1037. parser.ParseCLI(argv);
  1038. } catch (const args::Help&) {
  1039. std::cout << parser;
  1040. return 0;
  1041. } catch (const args::Error& e) {
  1042. std::cerr << parser;
  1043. std::cerr << e.what() << '\n';
  1044. return 1;
  1045. }
  1046. dds::install_signal_handlers();
  1047. dds::log::current_log_level = cli.log_level.Get();
  1048. try {
  1049. if (cli._verify_ident) {
  1050. std::cout << "yes\n";
  1051. return 0;
  1052. } else if (compile_file.cmd) {
  1053. return compile_file.run();
  1054. } else if (build.cmd) {
  1055. return build.run();
  1056. } else if (sdist.cmd) {
  1057. return sdist.run();
  1058. } else if (repo.cmd) {
  1059. return repo.run();
  1060. } else if (repoman.cmd) {
  1061. return repoman.run();
  1062. } else if (catalog.cmd) {
  1063. return catalog.run();
  1064. } else if (build_deps.cmd) {
  1065. return build_deps.run();
  1066. } else {
  1067. assert(false);
  1068. std::terminate();
  1069. }
  1070. } catch (const dds::user_cancelled&) {
  1071. dds_log(critical, "Operation cancelled by user");
  1072. return 2;
  1073. } catch (const dds::error_base& e) {
  1074. dds_log(error, "{}", e.what());
  1075. dds_log(error, "{}", e.explanation());
  1076. dds_log(error, "Refer: {}", e.error_reference());
  1077. return 1;
  1078. } catch (const std::exception& e) {
  1079. dds_log(critical, e.what());
  1080. return 2;
  1081. }
  1082. }
  1083. #if NEO_OS_IS_WINDOWS
  1084. std::string wstr_to_u8str(std::wstring_view in) {
  1085. if (in.empty()) {
  1086. return "";
  1087. }
  1088. auto req_size = ::WideCharToMultiByte(CP_UTF8,
  1089. 0,
  1090. in.data(),
  1091. static_cast<int>(in.size()),
  1092. nullptr,
  1093. 0,
  1094. nullptr,
  1095. nullptr);
  1096. neo_assert(invariant,
  1097. req_size > 0,
  1098. "Failed to convert given unicode string for main() argv",
  1099. req_size,
  1100. std::system_category().message(::GetLastError()),
  1101. ::GetLastError());
  1102. std::string ret;
  1103. ret.resize(req_size);
  1104. ::WideCharToMultiByte(CP_UTF8,
  1105. 0,
  1106. in.data(),
  1107. static_cast<int>(in.size()),
  1108. ret.data(),
  1109. static_cast<int>(ret.size()),
  1110. nullptr,
  1111. nullptr);
  1112. return ret;
  1113. }
  1114. int wmain(int argc, wchar_t** argv) {
  1115. std::vector<std::string> u8_argv;
  1116. ::setlocale(LC_ALL, ".utf8");
  1117. for (int i = 1; i < argc; ++i) {
  1118. u8_argv.emplace_back(wstr_to_u8str(argv[i]));
  1119. }
  1120. return main_fn(u8_argv);
  1121. }
  1122. #else
  1123. int main(int argc, char** argv) { return main_fn({argv + 1, argv + argc}); }
  1124. #endif