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.

1211 líneas
43KB

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