From d6500b9d7ee5cf68a7507f9d4d726ffb767d827a Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 1 Feb 2023 13:47:47 +0300 Subject: Implement system package manager query and install support for Fedora Note that the main/devel name resolution based on the project name still needs to be fixed. --- bpkg/system-package-manager-fedora.test.cxx | 382 ++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 bpkg/system-package-manager-fedora.test.cxx (limited to 'bpkg/system-package-manager-fedora.test.cxx') diff --git a/bpkg/system-package-manager-fedora.test.cxx b/bpkg/system-package-manager-fedora.test.cxx new file mode 100644 index 0000000..2c5a976 --- /dev/null +++ b/bpkg/system-package-manager-fedora.test.cxx @@ -0,0 +1,382 @@ +// file : bpkg/system-package-manager-fedora.test.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include + +#undef NDEBUG +#include + +#include + +using namespace std; + +namespace bpkg +{ + using package_status = system_package_status_fedora; + using package_info_ = package_status::package_info; + using package = system_package_manager_fedora::simulation::package; + + using butl::manifest_parser; + using butl::manifest_parsing; + + // Usage: args[0] ... + // + // Where is one of: + // + // dnf-list ... result comes from stdin + // + // dnf-repoquery-requires result comes from stdin + // + // parse-name-value fedora-name value from stdin + // + // main-from-devel depends comes from stdin in + // the ` ` + // per line form + // + // build ... [--install [--no-fetch] ...] + // + // The stdin of the build command is used to read the simulation description + // which consists of lines in the following forms (blanks are ignored): + // + // manifest: + // + // Available package manifest for one of . If none is + // specified, then a stub is automatically added. + // + // dnf-list[-{fetched,installed}]: ... + // + // Values for simulation::dnf_list_*. If is the special `!` value, + // then make the entry empty. + // + // dnf-repoquery-requires[-fetched]: + // + // Values for simulation::dnf_repoquery_requires_*. If is the + // special `!` value, then make the entry empty. + // + // dnf_makecache-fail: true + // dnf-install-fail: true + // dnf-mark-install-fail: true + // + // Values for simulation::dnf_{makecache,install,mark_install}_fail_. + // + // While creating the system package manager always pretend to be the x86_64 + // Fedora host (x86_64-redhat-linux-gnu), regardless of the actual host + // platform. + // + int + main (int argc, char* argv[]) + try + { + assert (argc >= 2); // + + target_triplet host_triplet ("x86_64-redhat-linux-gnu"); + + string cmd (argv[1]); + + // @@ TODO: add option to customize? Maybe option before command? + // + os_release osr {"fedora", {}, "35", "", "Fedora Linux", "", ""}; + + if (cmd == "dnf-list") + { + assert (argc >= 3); // ... + + strings key; + vector pis; + for (int i (2); i != argc; ++i) + { + key.push_back (argv[i]); + pis.push_back (package_info_ (argv[i])); + } + + system_package_manager_fedora::simulation s; + s.dnf_list_.emplace (move (key), path ("-")); + + system_package_manager_fedora m (move (osr), + host_triplet, + false /* install */, + false /* fetch */, + nullopt /* progress */, + false /* yes */, + "sudo"); + m.simulate_ = &s; + + m.dnf_list (pis); + + for (const package_info_& pi: pis) + { + cout << pi.name << " '" + << pi.installed_version << "' '" + << pi.installed_arch << "' '" + << pi.candidate_version << "' '" + << pi.candidate_arch << "'\n"; + } + } + else if (cmd == "dnf-repoquery-requires") + { + assert (argc == 5); // + + package key {argv[2], argv[3], argv[4]}; + + system_package_manager_fedora::simulation s; + s.dnf_repoquery_requires_.emplace (key, path ("-")); + + system_package_manager_fedora m (move (osr), + host_triplet, + false /* install */, + false /* fetch */, + nullopt /* progress */, + false /* yes */, + "sudo"); + m.simulate_ = &s; + + for (const pair& d: + m.dnf_repoquery_requires (key.name, key.version, key.arch)) + { + cout << d.first << ' ' << d.second << '\n'; + } + } + else if (cmd == "parse-name-value") + { + assert (argc == 3); // + + package_name pn (argv[2]); + + string v; + getline (cin, v); + + package_status s ( + system_package_manager_fedora::parse_name_value ( + pn, v, false, false, false)); + + if (!s.main.empty ()) cout << "main: " << s.main << '\n'; + if (!s.devel.empty ()) cout << "devel: " << s.devel << '\n'; + if (!s.static_.empty ()) cout << "static: " << s.static_ << '\n'; + if (!s.doc.empty ()) cout << "doc: " << s.doc << '\n'; + if (!s.debuginfo.empty ()) cout << "debuginfo: " << s.debuginfo << '\n'; + if (!s.debugsource.empty ()) cout << "debugsource: " << s.debugsource << '\n'; + if (!s.common.empty ()) cout << "common: " << s.common << '\n'; + if (!s.extras.empty ()) + { + cout << "extras:"; + for (const string& e: s.extras) + cout << ' ' << e; + cout << '\n'; + } + } + else if (cmd == "main-from-devel") + { + assert (argc == 4); // + + string n (argv[2]); + string v (argv[3]); + vector> ds; + + for (string l; !eof (getline (cin, l)); ) + { + size_t p (l.find (' ')); + assert (p != string::npos); + + ds.emplace_back (string (l, 0, p), string (l, p + 1)); + } + + cout << system_package_manager_fedora::main_from_devel (n, v, ds) << '\n'; + } + else if (cmd == "build") + { + assert (argc >= 3); // ... + + strings qps; + map aps; + + // Parse ... + // + int argi (2); + for (; argi != argc; ++argi) + { + string a (argv[argi]); + + if (a.compare (0, 2, "--") == 0) + break; + + aps.emplace (a, available_packages {}); + qps.push_back (move (a)); + } + + // Parse --install [--no-fetch] + // + bool install (false); + bool fetch (true); + + for (; argi != argc; ++argi) + { + string a (argv[argi]); + + if (a == "--install") install = true; + else if (a == "--no-fetch") fetch = false; + else break; + } + + // Parse the description. + // + system_package_manager_fedora::simulation s; + + for (string l; !eof (getline (cin, l)); ) + { + if (l.empty ()) + continue; + + size_t p (l.find (':')); assert (p != string::npos); + string k (l, 0, p); + + if (k == "manifest") + { + size_t q (l.rfind (' ')); assert (q != string::npos); + string n (l, p + 2, q - p - 2); trim (n); + string f (l, q + 1); trim (f); + + auto i (aps.find (n)); + if (i == aps.end ()) + fail << "unknown package " << n << " in '" << l << "'"; + + i->second.push_back (make_available_from_manifest (n, f)); + } + else if ( + map* infos = + k == "dnf-list" ? &s.dnf_list_ : + k == "dnf-list-fetched" ? &s.dnf_list_fetched_ : + k == "dnf-list-installed" ? &s.dnf_list_installed_ : + nullptr) + { + size_t q (l.rfind (' ')); assert (q != string::npos); + string n (l, p + 2, q - p - 2); trim (n); + string f (l, q + 1); trim (f); + + strings ns; + for (size_t b (0), e (0); next_word (n, b, e); ) + ns.push_back (string (n, b, e - b)); + + if (f == "!") + f.clear (); + + infos->emplace (move (ns), path (move (f))); + } + else if (map* req = + k == "dnf-repoquery-requires" ? &s.dnf_repoquery_requires_ : + k == "dnf-repoquery-requires-fetched" ? &s.dnf_repoquery_requires_fetched_ : + nullptr) + { + size_t q (l.rfind (' ')); assert (q != string::npos); + string n (l, p + 2, q - p - 2); trim (n); + string f (l, q + 1); trim (f); + + q = n.rfind (' '); assert (q != string::npos); + string a (n, q + 1); + n.resize (q); + + q = n.find (' '); assert (q != string::npos); + + package pkg {string (n, 0, q), string (n, q + 1), move (a)}; + + if (f == "!") + f.clear (); + + req->emplace (move (pkg), path (move (f))); + } + else if (k == "dnf-makecache-fail") + { + s.dnf_makecache_fail_ = true; + } + else if (k == "dnf-install-fail") + { + s.dnf_install_fail_ = true; + } + else if (k == "dnf-mark-install-fail") + { + s.dnf_mark_install_fail_ = true; + } + else + fail << "unknown keyword '" << k << "' in simulation description"; + } + + // Fallback to stubs and sort in the version descending order. + // + for (pair& p: aps) + { + if (p.second.empty ()) + p.second.push_back (make_available_stub (p.first)); + + sort_available (p.second); + } + + system_package_manager_fedora m (move (osr), + host_triplet, + install, + fetch, + nullopt /* progress */, + false /* yes */, + "sudo"); + m.simulate_ = &s; + + // Query each package. + // + for (const string& n: qps) + { + package_name pn (n); + + const system_package_status* s (*m.pkg_status (pn, &aps[n])); + + assert (*m.pkg_status (pn, nullptr) == s); // Test caching. + + if (s == nullptr) + fail << "no installed " << (install ? "or available " : "") + << "system package for " << pn; + + cout << pn << ' ' << s->version + << " (" << s->system_name << ' ' << s->system_version << ") "; + + switch (s->status) + { + case package_status::installed: cout << "installed"; break; + case package_status::partially_installed: cout << "part installed"; break; + case package_status::not_installed: cout << "not installed"; break; + } + + cout << '\n'; + } + + // Install if requested. + // + if (install) + { + assert (argi != argc); // ... + + vector ips; + for (; argi != argc; ++argi) + ips.push_back (package_name (argv[argi])); + + m.pkg_install (ips); + } + } + else + fail << "unknown command '" << cmd << "'"; + + return 0; + } + catch (const failed&) + { + return 1; + } +} + +int +main (int argc, char* argv[]) +{ + return bpkg::main (argc, argv); +} -- cgit v1.1