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-debian.cxx | 5 +- bpkg/system-package-manager-fedora.cxx | 1665 ++++++++++++++++++++ bpkg/system-package-manager-fedora.hxx | 305 ++++ bpkg/system-package-manager-fedora.test.cxx | 382 +++++ bpkg/system-package-manager-fedora.test.testscript | 1310 +++++++++++++++ bpkg/system-package-manager.cxx | 20 + doc/manual.cli | 7 +- 7 files changed, 3691 insertions(+), 3 deletions(-) create mode 100644 bpkg/system-package-manager-fedora.cxx create mode 100644 bpkg/system-package-manager-fedora.hxx create mode 100644 bpkg/system-package-manager-fedora.test.cxx create mode 100644 bpkg/system-package-manager-fedora.test.testscript diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx index 998c4f4..4cad141 100644 --- a/bpkg/system-package-manager-debian.cxx +++ b/bpkg/system-package-manager-debian.cxx @@ -62,6 +62,7 @@ namespace bpkg if (pn != nullptr && pn->string ().compare (0, 3, "lib") == 0 && + pn->string ().size () > 3 && suffix (m, "-dev") && !(ns.size () > 1 && suffix (ns[1], "-dev"))) { @@ -909,7 +910,7 @@ namespace bpkg // non-libraries with the lib prefix (both of which we do not // recomment) will have to provide a manual mapping. // - if (n.compare (0, 3, "lib") == 0) + if (n.compare (0, 3, "lib") == 0 && n.size () > 3) { // Keep the main package name empty as an indication that it is to // be discovered. @@ -1283,7 +1284,7 @@ namespace bpkg assert (install_ && !installed_); installed_ = true; - // Collect and merge all the Debian packages/version for the specified + // Collect and merge all the Debian packages/versions for the specified // bpkg packages. // struct package diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx new file mode 100644 index 0000000..7e6990e --- /dev/null +++ b/bpkg/system-package-manager-fedora.cxx @@ -0,0 +1,1665 @@ +// file : bpkg/system-package-manager-fedora.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace butl; + +namespace bpkg +{ + using package_status = system_package_status_fedora; + + // Parse the fedora-name (or alike) value. + // + // Note that for now we treat all the packages from the non-main groups as + // extras omitting the -common package (assuming it's pulled by the main + // package) as well as -doc and -debug* unless requested with the + // extra_{doc,debug*} arguments. Note that we treat -static as -devel (since + // we can't know whether the static library is needed or not). + // + package_status system_package_manager_fedora:: + parse_name_value (const package_name& pn, + const string& nv, + bool extra_doc, + bool extra_debuginfo, + bool extra_debugsource) + { + auto split = [] (const string& s, char d) -> strings + { + strings r; + for (size_t b (0), e (0); next_word (s, b, e, d); ) + r.push_back (string (s, b, e - b)); + return r; + }; + + auto suffix = [] (const string& n, const string& s) -> bool + { + size_t nn (n.size ()); + size_t sn (s.size ()); + return nn > sn && n.compare (nn - sn, sn, s) == 0; + }; + + auto parse_group = [&split, &suffix] (const string& g, + const package_name* pn) + { + strings ns (split (g, ' ')); + + if (ns.empty ()) + fail << "empty package group"; + + package_status r; + + // Handle the "devel instead of main" special case for libraries. + // + // Note: the lib prefix check is based on the bpkg package name. + // + // Check that the following name does not end with -devel. This will be + // the only way to disambiguate the case where the library name happens + // to end with -devel (e.g., libfoo-devel libfoo-devel-devel). + // + { + string& m (ns[0]); + + if (pn != nullptr && + pn->string ().compare (0, 3, "lib") == 0 && + pn->string ().size () > 3 && + suffix (m, "-devel") && + !(ns.size () > 1 && suffix (ns[1], "-devel"))) + { + r = package_status ("", move (m)); + } + else + r = package_status (move (m)); + } + + // Handle the rest. + // + for (size_t i (1); i != ns.size (); ++i) + { + string& n (ns[i]); + + const char* w; + if (string* v = (suffix (n, (w = "-devel")) ? &r.devel : + suffix (n, (w = "-static")) ? &r.static_ : + suffix (n, (w = "-doc")) ? &r.doc : + suffix (n, (w = "-debuginfo")) ? &r.debuginfo : + suffix (n, (w = "-debugsource")) ? &r.debugsource : + suffix (n, (w = "-common")) ? &r.common : + nullptr)) + { + if (!v->empty ()) + fail << "multiple " << w << " package names in '" << g << "'" << + info << "did you forget to separate package groups with comma?"; + + *v = move (n); + } + else + r.extras.push_back (move (n)); + } + + return r; + }; + + strings gs (split (nv, ',')); + assert (!gs.empty ()); // *-name value cannot be empty. + + package_status r; + for (size_t i (0); i != gs.size (); ++i) + { + if (i == 0) // Main group. + r = parse_group (gs[i], &pn); + else + { + package_status g (parse_group (gs[i], nullptr)); + + if (!g.main.empty ()) r.extras.push_back (move (g.main)); + if (!g.devel.empty ()) r.extras.push_back (move (g.devel)); + if (!g.static_.empty ()) r.extras.push_back (move (g.static_)); + if (!g.doc.empty () && extra_doc) r.extras.push_back (move (g.doc)); + + if (!g.debuginfo.empty () && extra_debuginfo) + r.extras.push_back (move (g.debuginfo)); + + if (!g.debugsource.empty () && extra_debugsource) + r.extras.push_back (move (g.debugsource)); + + if (!g.common.empty () && false) r.extras.push_back (move (g.common)); + if (!g.extras.empty ()) r.extras.insert ( + r.extras.end (), + make_move_iterator (g.extras.begin ()), + make_move_iterator (g.extras.end ())); + } + } + + return r; + } + + // Attempt to determine the main package name from its -devel package based + // on the extracted (by dnf_repoquery_requires()) dependencies, passed as a + // list of the package name/version pairs. Return empty string if unable to. + // + string system_package_manager_fedora:: + main_from_devel (const string& devel_name, + const string& devel_ver, + const vector>& depends) + { + // For the main package we first look for a dependency with the + // -libs name and the devel_ver version. Failed that, we try + // just . + // + // Note that the order is important since for a mixed package we need to + // end up with the -libs subpackage rather than with the base package as, + // for example, in the following case: + // + // sqlite-devel 3.36.0-3.fc35 -> + // sqlite 3.36.0-3.fc35 + // sqlite-libs 3.36.0-3.fc35 + // + string devel_stem (devel_name, 0, devel_name.rfind ("-devel")); + + auto find = [&devel_ver, &depends] (const string& n) + { + auto i (find_if (depends.begin (), depends.end (), + [&n, &devel_ver] (const pair& d) + { + return d.first == n && d.second == devel_ver; + })); + + return i != depends.end () ? i->first : string (); + }; + + string r (find (devel_stem + "-libs")); + return !r.empty () ? r : find (devel_stem); + } + + static process_path dnf_path; + static process_path sudo_path; + + // Obtain the installed and candidate versions for the specified list of + // Fedora packages by executing `dnf list`. + // + // If the n argument is not 0, then only query the first n packages. + // + void system_package_manager_fedora:: + dnf_list (vector& pis, size_t n) + { + if (n == 0) + n = pis.size (); + + assert (n != 0 && n <= pis.size ()); + + // The --quiet option makes sure we don't get 'Last metadata expiration + // check: ' printed to stderr. It does not appear to affect + // error diagnostics (try specifying a single unknown package). + // + cstrings args { + "dnf", "list", + "--all", // Look for both installed and available. + "--cacheonly", // Don't automatically update the metadata. + "--quiet"}; + + for (size_t i (0); i != n; ++i) + { + package_info& pi (pis[i]); + + string& n (pi.name); + assert (!n.empty ()); + + pi.installed_version.clear (); + pi.candidate_version.clear (); + + pi.installed_arch.clear (); + pi.candidate_arch.clear (); + + args.push_back (n.c_str ()); + } + + // Note that `dnf list` fails if there are no matching packages to print. + // Thus, let's hack around this by adding the rpm package to the list, so + // that at least one package is always present and the command can never + // fail for that reason. + // + // Also note that we still allow the rpm package to appear in the + // specified package list. + // + bool rpm (false); + args.push_back ("rpm"); + + args.push_back (nullptr); + + // Run with the C locale to make sure there is no localization. + // + const char* evars[] = {"LC_ALL=C", nullptr}; + + try + { + if (dnf_path.empty () && !simulate_) + dnf_path = process::path_search (args[0]); + + process_env pe (dnf_path, evars); + + if (verb >= 3) + print_process (pe, args); + + // Redirect stdout to a pipe. For good measure also redirect stdin to + // /dev/null to make sure there are no prompts of any kind. + // + process pr; + if (!simulate_) + pr = process (dnf_path, + args, + -2 /* stdin */, + -1 /* stdout */, + 2 /* stderr */, + nullptr /* cwd */, + evars); + else + { + strings k; + for (size_t i (0); i != n; ++i) + k.push_back (pis[i].name); + + const path* f (nullptr); + if (installed_) + { + auto i (simulate_->dnf_list_installed_.find (k)); + if (i != simulate_->dnf_list_installed_.end ()) + f = &i->second; + } + if (f == nullptr && fetched_) + { + auto i (simulate_->dnf_list_fetched_.find (k)); + if (i != simulate_->dnf_list_fetched_.end ()) + f = &i->second; + } + if (f == nullptr) + { + auto i (simulate_->dnf_list_.find (k)); + if (i != simulate_->dnf_list_.end ()) + f = &i->second; + } + + diag_record dr (text); + print_process (dr, pe, args); + dr << " <" << (f == nullptr || f->empty () ? "/dev/null" : f->string ()); + + pr = process (process_exit (0)); + pr.in_ofd = f == nullptr || f->empty () + ? fdopen_null () + : (f->string () == "-" + ? fddup (stdin_fd ()) + : fdopen (*f, fdopen_mode::in)); + } + + try + { + ifdstream is (move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit); + + // The output of `dnf list ...` is the 2 groups of lines + // in the following form: + // + // Installed Packages + // . 13.0.0-3.fc35 @ + // . 69.1-6.fc35 @ + // Available Packages + // . 13.0.1-1.fc35 + // . 1.2.11-32.fc35 + // + // Where unknown packages are omitted. The lines order does not + // necessarily match the order of the packages on the command line. + // It looks like there should be not blank lines but who really knows. + // + // Note also that if a package appears in the 'Installed Packages' + // group, then it only appears in the 'Available Packages' if the + // candidate version is better. Only the single (best) available + // version is listed, which we call the candidate version. + // + { + auto df = make_diag_frame ( + [&pe, &args] (diag_record& dr) + { + dr << info << "while parsing output of "; + print_process (dr, pe, args); + }); + + // Keep track of whether we are inside of the 'Installed Packages' + // or 'Available Packages' sections. + // + optional installed; + + for (string l; !eof (getline (is, l)); ) + { + if (l == "Installed Packages") + { + if (installed) + fail << "unexpected line '" << l << "'"; + + installed = true; + continue; + } + + if (l == "Available Packages") + { + if (installed && !*installed) + fail << "duplicate line '" << l << "'"; + + installed = false; + continue; + } + + if (!installed) + fail << "unexpected line '" << l << "'"; + + // Parse the package name. + // + size_t e (l.find (' ')); + + if (l.empty () || e == 0) + fail << "expected package name in '" << l << "'"; + + if (e == string::npos) + fail << "expected package version in '" << l << "'"; + + string p (l, 0, e); + + // Parse the package version. + // + size_t b (l.find_first_not_of (' ', e + 1)); + + if (b == string::npos) + fail << "expected package version in '" << l << "'"; + + // It doesn't not seem that the repository id can be absent. Even + // if the package is installed manually it is assumed to come from + // some special repository (@commandline, etc). For example: + // + // # dnf install ./libsigc++30-3.0.7-2.fc35.x86_64.rpm + // # rpm -i ./libsigc++30-devel-3.0.7-2.fc35.x86_64.rpm + // # dnf list --quiet libsigc++30.x86_64 libsigc++30-devel.x86_64 + // Installed Packages + // libsigc++30.x86_64 3.0.7-2.fc35 @@commandline + // libsigc++30-devel.x86_64 3.0.7-2.fc35 @@System + // + // Thus, we assume that the version is always followed with the + // space character. + // + e = l.find (' ', b + 1); + + if (e == string::npos) + fail << "expected package repository in '" << l << "'"; + + string v (l, b, e - b); + + // While we don't really care about the rest of the line, let's + // verify that it contains a repository id, for good measure. + // + b = l.find_first_not_of (' ', e + 1); + + if (b == string::npos) + fail << "expected package repository in '" << l << "'"; + + // Separate the architecture from the package name. + // + e = p.rfind ('.'); + + if (e == string::npos || e == 0 || e == p.size () - 1) + fail << "can't extract architecture for package " << p + << " in '" << l << "'"; + + string a (p, e + 1); + + // Skip the package if its architecture differs from the host + // architecture. + // + // @@ TODO: arch translation. + // + if (a != host_.cpu && a != "noarch") + continue; + + p.resize (e); + + if (p == "rpm") + rpm = true; + + // Find the package info to update. + // + auto i (find_if (pis.begin (), pis.end (), + [&p] (const package_info& pi) + {return pi.name == p;})); + + if (i == pis.end ()) + { + // Skip the special rpm package which may not be present in the + // list. + // + if (p == "rpm") + continue; + + fail << "unexpected package " << p << '.' << a << ' ' << v + << " in '" << l << "'"; + } + + string& ver (*installed + ? i->installed_version + : i->candidate_version); + + if (!ver.empty ()) + fail << "multiple " << (*installed ? "installed " : "available ") + << "versions of package " << p << '.' << a << + info << "version: " << ver << + info << "version: " << v; + + ver = move (v); + + (*installed ? i->installed_arch : i->candidate_arch) = move (a); + } + } + + is.close (); + } + catch (const io_error& e) + { + if (pr.wait ()) + fail << "unable to read " << args[0] << " list output: " << e; + + // Fall through. + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << args[0] << " list exited with non-zero code"; + + if (verb < 3) + { + dr << info << "command line: "; + print_process (dr, pe, args); + } + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + + if (!rpm) + fail << "rpm package doesn't exist"; + + // Note that if a Fedora package is installed but the repository doesn't + // contain a better version, then this package won't appear in the + // 'Available Packages' section of the `dnf list` output and thus the + // candidate_version will stay empty. Let's set it to the installed + // version in this case to be consistent with the Debian's semantics and + // keep the Fedora and Debian system package manager implementations + // aligned. + // + for (size_t i (0); i != n; ++i) + { + package_info& pi (pis[i]); + + if (pi.candidate_version.empty () && !pi.installed_version.empty ()) + { + pi.candidate_version = pi.installed_version; + pi.candidate_arch = pi.installed_arch; + } + } + } + + // Execute `dnf repoquery --requires` for the specified + // package/version/architecture and return its dependencies as a list of the + // name/version pairs. + // + // It is expected that the specified package/version/architecture is known + // (e.g., returned by the `dnf list` command). Note that if that's not the + // case (can happen due to a race), then an empty list is returned. This, + // however, is ok for our current usage since in this case we will shortly + // fail with the 'unable to guess main package' error anyway. + // + // Note that the returned dependencies are always of the host architecture + // or noarch. For example: + // + // dhcp-client-12:4.4.3-4.P1.fc35.x86_64 -> + // dhcp-common-12:4.4.3-4.P1.fc35.noarch + // coreutils-8.32-36.fc35.x86_64 + // ... + // + // rust-uuid+std-devel-1.2.1-1.fc35.noarch -> + // rust-uuid-devel-1.2.1-1.fc35.noarch + // cargo-1.65.0-1.fc35.x86_64 + // + vector> system_package_manager_fedora:: + dnf_repoquery_requires (const string& name, + const string& ver, + const string& arch) + { + assert (!name.empty () && !ver.empty () && !arch.empty ()); + + // Qualify the package with the architecture suffix. + // + // Note that for reasons unknown, the below command may still print some + // dependencies with different architecture (see the below example). It + // feels sensible to just skip them. + // + string spec (name + '-' + ver + '.' + arch); + + // The --quiet option makes sure we don't get 'Last metadata expiration + // check: ' printed to stderr. It does not appear to affect + // error diagnostics (try specifying an unknown option). + // + const char* args[] = { + "dnf", "repoquery", "--requires", + "--quiet", + "--cacheonly", // Don't automatically update the metadata. + "--resolve", // Resolve requirements to packages/versions. + "--qf", "%{name} %{arch} %{epoch}:%{version}-%{release}", + spec.c_str (), + nullptr}; + + // Note that for this command there seems to be no need to run with the C + // locale since the output is presumably not localizable. But let's do it + // for good measure. + // + const char* evars[] = {"LC_ALL=C", nullptr}; + + vector> r; + try + { + if (dnf_path.empty () && !simulate_) + dnf_path = process::path_search (args[0]); + + process_env pe (dnf_path, evars); + + if (verb >= 3) + print_process (pe, args); + + // Redirect stdout to a pipe. For good measure also redirect stdin to + // /dev/null to make sure there are no prompts of any kind. + // + process pr; + if (!simulate_) + pr = process (dnf_path, + args, + -2 /* stdin */, + -1 /* stdout */, + 2 /* stderr */, + nullptr /* cwd */, + evars); + else + { + simulation::package k {name, ver, arch}; + + const path* f (nullptr); + if (fetched_) + { + auto i (simulate_->dnf_repoquery_requires_fetched_.find (k)); + if (i != simulate_->dnf_repoquery_requires_fetched_.end ()) + f = &i->second; + } + if (f == nullptr) + { + auto i (simulate_->dnf_repoquery_requires_.find (k)); + if (i != simulate_->dnf_repoquery_requires_.end ()) + f = &i->second; + } + + diag_record dr (text); + print_process (dr, pe, args); + dr << " <" << (f == nullptr || f->empty () ? "/dev/null" : f->string ()); + + pr = process (process_exit (0)); + pr.in_ofd = f == nullptr || f->empty () + ? fdopen_null () + : (f->string () == "-" + ? fddup (stdin_fd ()) + : fdopen (*f, fdopen_mode::in)); + } + + try + { + ifdstream is (move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit); + + // The output of the command will be the sequence of the package lines + // in the ` ` form (per the -qf option above). So + // for example for the libicu-devel-69.1-6.fc35.x86_64 package it is + // as follows: + // + // bash i686 0:5.1.8-3.fc35 + // bash x86_64 0:5.1.8-3.fc35 + // glibc i686 0:2.34-49.fc35 + // glibc x86_64 0:2.34-49.fc35 + // libicu x86_64 0:69.1-6.fc35 + // libicu-devel i686 0:69.1-6.fc35 + // libicu-devel x86_64 0:69.1-6.fc35 + // pkgconf-pkg-config i686 0:1.8.0-1.fc35 + // pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + // + // Note that there is also a self-dependency. + // + for (string l; !eof (getline (is, l)); ) + { + // Parse the package name. + // + size_t e (l.find (' ')); + + if (l.empty () || e == 0) + fail << "expected package name in '" << l << "'"; + + if (e == string::npos) + fail << "expected package architecture in '" << l << "'"; + + string p (l, 0, e); + + // Parse the package architecture. + // + size_t b (e + 1); + e = l.find (' ', b); + + if (e == string::npos) + fail << "expected package version in '" << l << "'"; + + string a (l, b, e - b); + if (a.empty ()) + fail << "expected package architecture in '" << l << "'"; + + // Parse the package version. + // + string v (l, e + 1); + + // Strip the '0:' epoch from the package version to align with + // versions retrieved by other functions (dnf_list(), etc). + // + e = v.find (':'); + if (e == string::npos || e == 0) + fail << "no epoch for package version in '" << l << "'"; + + if (e == 1 && v[0] == '0') + v.erase (0, 2); + + // Skip a potential self-dependency and dependencies of a different + // architecture. + // + if (p == name || (a != host_.cpu && a != "noarch")) + continue; + + r.emplace_back (move (p), move (v)); + } + + is.close (); + } + catch (const io_error& e) + { + if (pr.wait ()) + fail << "unable to read " << args[0] << " repoquery --requires " + << "output: " << e; + + // Fall through. + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << args[0] << " repoquery --requires exited with non-zero code"; + + if (verb < 3) + { + dr << info << "command line: "; + print_process (dr, pe, args); + } + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + + return r; + } + + // Prepare the common options for commands which update the system. + // + pair system_package_manager_fedora:: + dnf_common (const char* command) + { + cstrings args; + + if (!sudo_.empty ()) + args.push_back (sudo_.c_str ()); + + args.push_back ("dnf"); + args.push_back (command); + + // Map our verbosity/progress to dnf --quiet and --verbose options. + // + // Note that all the diagnostics, including the progress indication and + // general information (like what's being installed) but excluding error + // messages, is printed to stdout. So we fix this by redirecting stdout to + // stderr. By default the progress bar for network transfers is printed, + // unless stdout is not a terminal. The --quiet option disables printing + // the plan and all the progress indication, but not the confirmation + // prompt nor error messages. + // + if (progress_ && *progress_) + { + // Print the progress bar by default, unless this is not a terminal + // (there is no way to force it). + } + else if (verb == 0 || (progress_ && !*progress_)) + { + args.push_back ("--quiet"); + } + + if (yes_) + { + args.push_back ("--assumeyes"); + } + else if (!stderr_term) + { + // Suppress any prompts if stderr is not a terminal for good measure. + // + args.push_back ("--assumeno"); + } + + try + { + const process_path* pp (nullptr); + + if (!sudo_.empty ()) + { + if (sudo_path.empty () && !simulate_) + sudo_path = process::path_search (args[0]); + + pp = &sudo_path; + } + else + { + if (dnf_path.empty () && !simulate_) + dnf_path = process::path_search (args[0]); + + pp = &dnf_path; + } + + return pair (move (args), *pp); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + } + + // Execute `dnf makecache` to download and cache the repositories metadata. + // + void system_package_manager_fedora:: + dnf_makecache () + { + pair args_pp (dnf_common ("makecache")); + + cstrings& args (args_pp.first); + const process_path& pp (args_pp.second); + + args.push_back ("--refresh"); + args.push_back (nullptr); + + try + { + if (verb >= 2) + print_process (args); + else if (verb == 1) + text << "updating " << os_release_.name_id + << " repositories metadata..."; + + process pr; + if (!simulate_) + pr = process (pp, args, 0 /* stdin */, 2 /* stdout */); + else + { + print_process (args); + pr = process (process_exit (simulate_->dnf_makecache_fail_ ? 1 : 0)); + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << "dnf makecache exited with non-zero code"; + + if (verb < 2) + { + dr << info << "command line: "; + print_process (dr, args); + } + } + + if (verb == 1) + text << "updated " << os_release_.name_id << " repositories metadata"; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + } + + // Execute `dnf install` to install the specified packages/versions (e.g., + // libfoo or libfoo-1.2.3-1.fc35) and then `dnf mark install` to mark the + // specified packages as installed by user. + // + // @@ TODO: need to understand this strange semantics better. E.g., what + // happens on upgrade? + // + void system_package_manager_fedora:: + dnf_install (const strings& pkgs) + { + assert (!pkgs.empty ()); + + // Install. + // + { + pair args_pp (dnf_common ("install")); + + cstrings& args (args_pp.first); + const process_path& pp (args_pp.second); + + // Note that we can't use --cacheonly here to prevent the metadata + // update, since the install command then expects the package RPM files + // to also be cached and fails if that's not the case. Thus we have to + // override the metadata_expire=never configuration option instead. + // Which makes the whole thing quite hairy and of dubious value -- there + // is nothing wrong with letting it re-fetch the metadata during install + // (which in fact may save us from attempting to download no longer + // existing packages). + // +#if 0 + args.push_back ("--setopt=metadata_expire=never"); +#endif + + for (const string& p: pkgs) + args.push_back (p.c_str ()); + + args.push_back (nullptr); + + try + { + if (verb >= 2) + print_process (args); + else if (verb == 1) + text << "installing " << os_release_.name_id << " packages..."; + + process pr; + if (!simulate_) + pr = process (pp, args, 0 /* stdin */, 2 /* stdout */); + else + { + print_process (args); + pr = process (process_exit (simulate_->dnf_install_fail_ ? 100 : 0)); + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << "dnf install exited with non-zero code"; + + if (verb < 2) + { + dr << info << "command line: "; + print_process (dr, args); + } + + dr << info << "consider resolving the issue manually and retrying " + << "the bpkg command"; + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + } + + // Mark as installed. + // + { + pair args_pp (dnf_common ("mark")); + + cstrings& args (args_pp.first); + const process_path& pp (args_pp.second); + + args.push_back ("install"); + args.push_back ("--cacheonly"); + + for (const string& p: pkgs) + args.push_back (p.c_str ()); + + args.push_back (nullptr); + + try + { + if (verb >= 2) + print_process (args); + + process pr; + if (!simulate_) + pr = process (pp, args, 0 /* stdin */, 2 /* stdout */); + else + { + print_process (args); + pr = process (process_exit (simulate_->dnf_mark_install_fail_ ? 1 : 0)); + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << "dnf mark install exited with non-zero code"; + + if (verb < 2) + { + dr << info << "command line: "; + print_process (dr, args); + } + + dr << info << "consider resolving the issue manually and retrying " + << "the bpkg command"; + } + + if (verb == 1) + text << "installed " << os_release_.name_id << " packages"; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + } + } + + optional system_package_manager_fedora:: + pkg_status (const package_name& pn, const available_packages* aps) + { + // For now we ignore -doc and -debug* package components (but we may want + // to have options controlling this later). Note also that we assume + // -common is pulled automatically by the base package so we ignore it as + // well (see equivalent logic in parse_name_value()). + // + bool need_doc (false); + bool need_debuginfo (false); + bool need_debugsource (false); + + // First check the cache. + // + { + auto i (status_cache_.find (pn)); + + if (i != status_cache_.end ()) + return i->second ? &*i->second : nullptr; + + if (aps == nullptr) + return nullopt; + } + + vector candidates; + + // Translate our package name to the Fedora package names. + // + { + auto df = make_diag_frame ( + [this, &pn] (diag_record& dr) + { + dr << info << "while mapping " << pn << " to " + << os_release_.name_id << " package name"; + }); + + strings ns; + if (!aps->empty ()) + ns = system_package_names (*aps, + os_release_.name_id, + os_release_.version_id, + os_release_.like_ids); + if (ns.empty ()) + { + // Attempt to automatically translate our package name. Failed that we + // should try to use the project name, if present, as a fallback. + // + const string& n (pn.string ()); + + // Note that theoretically different available packages can have + // different project names. But taking it form the latest version + // feels good enough. + // + const shared_ptr& ap (!aps->empty () + ? aps->front ().first + : nullptr); + + string f (ap != nullptr && ap->project && *ap->project != pn + ? ap->project->string () + : empty_string); + + // The best we can do in trying to detect whether this is a library is + // to check for the lib prefix. Libraries without the lib prefix and + // non-libraries with the lib prefix (both of which we do not + // recomment) will have to provide a manual mapping. + // + if (n.compare (0, 3, "lib") == 0 && n.size () > 3) + { + // If there is no project name let's try to use the package name + // with the lib prefix stripped as a fallback. Note that naming + // library packages without the lib prefix is quite common in Fedora + // (xerces-c, uuid-c++, etc). + // + if (f.empty ()) + f = string (n, 3); + + f += "-devel"; + + // Keep the base package name empty as an indication that it is to + // be discovered. + // + candidates.push_back (package_status ("", n + "-devel", move (f))); + } + else + candidates.push_back (package_status (n, "", move (f))); + } + else + { + // Parse each manual mapping. + // + for (const string& n: ns) + { + package_status s (parse_name_value (pn, + n, + need_doc, + need_debuginfo, + need_debugsource)); + + // Suppress duplicates for good measure based on the base package + // name (and falling back to -devel if empty). + // + auto i (find_if (candidates.begin (), candidates.end (), + [&s] (const package_status& x) + { + // Note that it's possible for one mapping to be + // specified as -devel only while the other as + // main and -devel. + // + return s.main.empty () || x.main.empty () + ? s.devel == x.devel + : s.main == x.main; + })); + if (i == candidates.end ()) + candidates.push_back (move (s)); + else + { + // Should we verify the rest matches for good measure? But what + // if we need to override, as in: + // + // fedora_35-name: libfoo libfoo-bar-devel + // fedora_34-name: libfoo libfoo-devel + // + // Note that for this to work we must get fedora_35 values before + // fedora_34, which is the semantics guaranteed by + // system_package_names(). + } + } + } + } + + // Guess unknown main package given the -devel package, its version, and + // architecture. + // + auto guess_main = [this, &pn] (package_status& s, + const string& ver, + const string& arch) + { + vector> depends ( + dnf_repoquery_requires (s.devel, ver, arch)); + + s.main = main_from_devel (s.devel, ver, depends); + + if (s.main.empty ()) + { + diag_record dr (fail); + dr << "unable to guess main " << os_release_.name_id + << " package for " << s.devel << ' ' << ver << + info << "depends on"; + + + for (auto b (depends.begin ()), i (b); i != depends.end (); ++i) + { + dr << (i == b ? " " : ", ") << i->first << ' ' << i->second; + } + + dr << info << "consider specifying explicit mapping in " << pn + << " package manifest"; + } + }; + + // Calculate the package status from individual package components. + // Return nullopt if there is a component without installed or candidate + // version (which means the package cannot be installed). + // + // The main argument specifies the size of the main group. Only components + // from this group are considered for partially_installed determination. + // + // @@ TODO: we should probably prioritize partially installed with fully + // installed main group. Add almost_installed next to partially_installed? + // + using status_type = package_status::status_type; + + auto status = [] (const vector& pis, size_t main) + -> optional + { + bool i (false), u (false); + + for (size_t j (0); j != pis.size (); ++j) + { + const package_info& pi (pis[j]); + + if (pi.installed_version.empty ()) + { + if (pi.candidate_version.empty ()) + return nullopt; + + u = true; + } + else if (j < main) + i = true; + } + + return (!u ? package_status::installed : + !i ? package_status::not_installed : + package_status::partially_installed); + }; + + // First, choose between the package name-based and project-based system + // package names. + // + for (package_status& ps: candidates) + { + vector& pis (ps.package_infos); + + // Query both main and fallback packages with a single dns_list() + // invocation. + // + if (!ps.main.empty ()) pis.emplace_back (ps.main); + if (!ps.devel.empty ()) pis.emplace_back (ps.devel); + if (!ps.fallback.empty ()) pis.emplace_back (ps.fallback); + if (!ps.static_.empty ()) pis.emplace_back (ps.static_); + if (!ps.doc.empty () && need_doc) pis.emplace_back (ps.doc); + + if (!ps.debuginfo.empty () && need_debuginfo) + pis.emplace_back (ps.debuginfo); + + if (!ps.debugsource.empty () && need_debugsource) + pis.emplace_back (ps.debugsource); + + if (!ps.common.empty () && false) pis.emplace_back (ps.common); + ps.package_infos_main = pis.size (); + for (const string& n: ps.extras) pis.emplace_back (n); + + dnf_list (pis); + + // If the project-based (fallback) system package name is specified, + // then choose between the main/devel and fallback names depending on + // which of them is known to the system package manager. + // + // Specifically, if the main/devel system package exists we use that. + // Otherwise, if the fallback system package exists we use that and fail + // otherwise. + // + if (!ps.fallback.empty ()) + { + assert (pis.size () > 1); // devel, fallback,... or main, fallback,... + + // Either devel or main is specified. + // + bool devel (!ps.devel.empty ()); + assert (devel == ps.main.empty ()); + + string& name (devel ? ps.devel : ps.main); + + package_info& mi (pis[0]); // Main/devel package info. + package_info& fi (pis[1]); // Fallback package info. + + if (mi.unknown ()) + { + if (fi.known ()) + { + name = move (ps.fallback); + mi = move (fi); + } + else + { + // @@ This feels incorrect: there can be another candidate that is + // found. Double-check Debian semantics. + // + fail << "unable to guess " << (devel ? "devel" : "main") + << ' ' << os_release_.name_id << " package for " << pn << + info << "neither " << name << " nor " << ps.fallback + << ' ' << os_release_.name_id << " package exists" << + info << "consider specifying explicit mapping in " << pn + << " package manifest"; + } + } + + // Whether it was used or not, cleanup the fallback information. + // + ps.fallback.clear (); + pis.erase (pis.begin () + 1); + --ps.package_infos_main; + } + } + + // Next look for an already fully installed package. + // + optional r; + + { + diag_record dr; // Ambiguity diagnostics. + + for (package_status& ps: candidates) + { + vector& pis (ps.package_infos); + + // Handle the unknown main package. + // + if (ps.main.empty ()) + { + const package_info& devel (pis.front ()); + + // Note that at this stage we can only use the installed -devel + // package (since the candidate version may change after fetch). + // + if (devel.installed_version.empty ()) + continue; + + guess_main (ps, devel.installed_version, devel.installed_arch); + pis.emplace (pis.begin (), ps.main); + ps.package_infos_main++; + dnf_list (pis, 1); + } + + optional s (status (pis, ps.package_infos_main)); + + if (!s || *s != package_status::installed) + continue; + + const package_info& main (pis.front ()); + + ps.status = *s; + ps.system_name = main.name; + ps.system_version = main.installed_version; + + if (!r) + { + r = move (ps); + continue; + } + + if (dr.empty ()) + { + dr << fail << "multiple installed " << os_release_.name_id + << " packages for " << pn << + info << "candidate: " << r->main << " " << r->system_version; + } + + dr << info << "candidate: " << ps.main << " " << ps.system_version; + } + + if (!dr.empty ()) + dr << info << "consider specifying the desired version manually"; + } + + // Finally look for available versions if we are allowed to install. + // + if (!r && install_) + { + // If we weren't instructed to fetch or we already fetched, then we + // don't need to re-run dnf_list(). + // + bool requery; + if ((requery = fetch_ && !fetched_)) + { + dnf_makecache (); + fetched_ = true; + } + + { + diag_record dr; // Ambiguity diagnostics. + + for (package_status& ps: candidates) + { + vector& pis (ps.package_infos); + + if (requery) + dnf_list (pis); + + // Handle the unknown main package. + // + if (ps.main.empty ()) + { + const package_info& devel (pis.front ()); + + // Note that this time we use the candidate version. + // + if (devel.candidate_version.empty ()) + continue; // Not installable. + + guess_main (ps, devel.candidate_version, devel.candidate_arch); + pis.emplace (pis.begin (), ps.main); + ps.package_infos_main++; + dnf_list (pis, 1); + } + + optional s (status (pis, ps.package_infos_main)); + + if (!s) + { + ps.main.clear (); // Not installable. + continue; + } + + assert (*s != package_status::installed); // Sanity check. + + const package_info& main (pis.front ()); + + // Note that if we are installing something for this main package, + // then we always go for the candidate version even though it may + // have an installed version that may be good enough (especially if + // what we are installing are extras). The reason is that it may as + // well not be good enough (especially if we are installing the + // -devel package) and there is no straightforward way to change our + // mind. + // + ps.status = *s; + ps.system_name = main.name; + ps.system_version = main.candidate_version; + + // Prefer partially installed to not installed. This makes detecting + // ambiguity a bit trickier so we handle partially installed here + // and not installed in a separate loop below. + // + if (ps.status != package_status::partially_installed) + continue; + + if (!r) + { + r = move (ps); + continue; + } + + auto print_missing = [&dr] (const package_status& s) + { + for (const package_info& pi: s.package_infos) + if (pi.installed_version.empty ()) + dr << ' ' << pi.name; + }; + + if (dr.empty ()) + { + dr << fail << "multiple partially installed " + << os_release_.name_id << " packages for " << pn; + + dr << info << "candidate: " << r->main << " " << r->system_version + << ", missing components:"; + print_missing (*r); + } + + dr << info << "candidate: " << ps.main << " " << ps.system_version + << ", missing components:"; + print_missing (ps); + } + + if (!dr.empty ()) + dr << info << "consider fully installing the desired package " + << "manually and retrying the bpkg command"; + } + + if (!r) + { + diag_record dr; // Ambiguity diagnostics. + + for (package_status& ps: candidates) + { + if (ps.main.empty ()) + continue; + + assert (ps.status == package_status::not_installed); // Sanity check. + + if (!r) + { + r = move (ps); + continue; + } + + if (dr.empty ()) + { + dr << fail << "multiple available " << os_release_.name_id + << " packages for " << pn << + info << "candidate: " << r->main << " " << r->system_version; + } + + dr << info << "candidate: " << ps.main << " " << ps.system_version; + } + + if (!dr.empty ()) + dr << info << "consider installing the desired package manually and " + << "retrying the bpkg command"; + } + } + + if (r) + { + // Map the Fedora version to the bpkg version. But first strip the + // release from Fedora version ([:]-). + // + // Note that both the version and release parts are mandatory and may + // only contain alpha-numeric characters, `.`, `_`, `+`, `~`, and `^` + // (see the RPM spec file format documentation for details). + // + string sv (r->system_version, 0, r->system_version.rfind ('-')); + + optional v; + if (!aps->empty ()) + v = downstream_package_version (sv, + *aps, + os_release_.name_id, + os_release_.version_id, + os_release_.like_ids); + + if (!v) + { + // Fallback to using system version as downstream version. But first + // strip the epoch, if any. + // + size_t p (sv.find (':')); + if (p != string::npos) + sv.erase (0, p + 1); + + try + { + v = version (sv); + } + catch (const invalid_argument& e) + { + fail << "unable to map " << os_release_.name_id << " package " + << r->system_name << " version " << sv << " to bpkg package " + << pn << " version" << + info << os_release_.name_id << " version is not a valid bpkg " + << "version: " << e.what () << + info << "consider specifying explicit mapping in " << pn + << " package manifest"; + } + } + + r->version = move (*v); + } + + // Cache. + // + auto i (status_cache_.emplace (pn, move (r)).first); + return i->second ? &*i->second : nullptr; + } + + void system_package_manager_fedora:: + pkg_install (const vector& pns) + { + assert (!pns.empty ()); + + assert (install_ && !installed_); + installed_ = true; + + // Collect and merge all the Fedora packages/versions for the specified + // bpkg packages. + // + struct package + { + string name; + string version; // Empty if unspecified. + }; + vector pkgs; + + for (const package_name& pn: pns) + { + auto it (status_cache_.find (pn)); + assert (it != status_cache_.end () && it->second); + + const package_status& ps (*it->second); + + // At first it may seem we don't need to do anything for already fully + // installed packages. But it's possible some of them were automatically + // installed, meaning that they can be automatically removed if they no + // longer have any dependents (see dnf(8) for details). Which in + // turn means that things may behave differently depending on whether + // we've installed a package ourselves or if it was already installed. + // So instead we are going to also pass the already fully installed + // packages which will make sure they are all set to manually installed. + // But we must be careful not to force their upgrade. To achieve this + // we will specify the installed version as the desired version. + // + // Note also that for partially/not installed we don't specify the + // version, expecting the candidate version to be installed. + // + bool fi (ps.status == package_status::installed); + + for (const package_info& pi: ps.package_infos) + { + string n (pi.name); + string v (fi ? pi.installed_version : string ()); + + auto i (find_if (pkgs.begin (), pkgs.end (), + [&n] (const package& p) + { + return p.name == n; + })); + + if (i != pkgs.end ()) + { + if (i->version.empty ()) + i->version = move (v); + else + // Feels like this cannot happen since we always use the installed + // version of the package. + // + assert (i->version == v); + } + else + pkgs.push_back (package {move (n), move (v)}); + } + } + + // Install. + // + { + // Convert to the `dnf install` [-] form. + // + strings specs; + specs.reserve (pkgs.size ()); + for (const package& p: pkgs) + { + string s (p.name); + if (!p.version.empty ()) + { + s += '-'; + s += p.version; + } + specs.push_back (move (s)); + } + + dnf_install (specs); + } + + // Verify that versions we have promised in pkg_status() match what + // actually got installed. + // + { + vector pis; + + // Here we just check the main package component of each package. + // + for (const package_name& pn: pns) + { + const package_status& ps (*status_cache_.find (pn)->second); + + if (find_if (pis.begin (), pis.end (), + [&ps] (const package_info& pi) + { + return pi.name == ps.system_name; + }) == pis.end ()) + { + pis.push_back (package_info (ps.system_name)); + } + } + + dnf_list (pis); + + for (const package_name& pn: pns) + { + const package_status& ps (*status_cache_.find (pn)->second); + + auto i (find_if (pis.begin (), pis.end (), + [&ps] (const package_info& pi) + { + return pi.name == ps.system_name; + })); + assert (i != pis.end ()); + + const package_info& pi (*i); + + if (pi.installed_version != ps.system_version) + { + fail << "unexpected " << os_release_.name_id << " package version " + << "for " << ps.system_name << + info << "expected: " << ps.system_version << + info << "installed: " << pi.installed_version << + info << "consider retrying the bpkg command"; + } + } + } + } +} diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx new file mode 100644 index 0000000..16fe9a3 --- /dev/null +++ b/bpkg/system-package-manager-fedora.hxx @@ -0,0 +1,305 @@ +// file : bpkg/system-package-manager-fedora.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX +#define BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX + +#include + +#include +#include + +#include + +namespace bpkg +{ + // The system package manager implementation for Fedora and alike (Red Hat + // Enterprise Linux, CentOS, etc) using the DNF frontend. + // + // NOTE: the below description is also reproduced in the bpkg manual. + // + // For background, a library in Fedora is normally split up into several + // packages: the shared library package (e.g., libfoo), the development + // files package (e.g., libfoo-devel), the static library package (e.g., + // libfoo-static; may also be placed into the -devel package), the + // documentation files package (e.g., libfoo-doc), the debug symbols and + // source files packages (e.g., libfoo-debuginfo and libfoo-debugsource), + // and the common or architecture-independent files (e.g., libfoo-common). + // All the packages except -devel are optional and there is quite a bit of + // variability here. In particular, the lib prefix in libfoo is not a + // requirement (unlike in Debian) and is normally present only if upstream + // name has it (see some examples below). + // + // For mixed packages which include both applications and libraries, the + // shared library package normally has the -libs suffix (e.g., foo-libs). + // Such packages may have separate -debuginfo packages for applications and + // libraries (e.g. openssl-debuginfo and openssl-libs-debuginfo). + // + // A package name may also include an upstream version based suffix if + // multiple versions of the package can be installed simultaneously (e.g., + // libfoo1.1 libfoo1.1-devel, libfoo2 libfoo2-devel). + // + // Terminology-wise, the term "base package" (sometime also "main package") + // normally refers to either the application or shared library package (as + // decided by the package maintainer in the spec file) with the suffixed + // packages (-devel, -doc, etc) called "subpackages". + // + // Here are a few examples: + // + // libpq libpq-devel + // + // zlib zlib-devel zlib-static + // + // xerces-c xerces-c-devel xerces-c-doc + // + // libsigc++20 libsigc++20-devel libsigc++20-doc + // libsigc++30 libsigc++30-devel libsigc++30-doc + // + // icu libicu libicu-devel libicu-doc + // + // openssl openssl-libs openssl-devel openssl-static + // openssl1.1 openssl1.1-devel + // + // curl libcurl libcurl-devel + // + // sqlite sqlite-libs sqlite-devel sqlite-doc + // + // community-mysql community-mysql-libs community-mysql-devel + // community-mysql-common community-mysql-server + // + // ncurses ncurses-libs ncurses-c++-libs ncurses-devel ncurses-static + // + // keyutils keyutils-libs keyutils-libs-devel + // + // Based on that, it seems our best bet when trying to automatically map our + // library package name to Fedora package names is to go for the -devel + // package first and figure out the shared library package from that based + // on the fact that the -devel package should have the == dependency on the + // shared library package with the same version and its name should normally + // start with the -devel package's stem and be potentially followed with the + // -libs suffix. Failed to find the -devel package, we may re-try but now + // using the project name instead of the package name (see, for example, + // openssl, sqlite). + // + // For application packages there is normally no -devel packages but + // -debug*, -doc, and -common are plausible. + // + // The format of the fedora-name (or alike) manifest value is a comma- + // separated list of one or more package groups: + // + // [, ...] + // + // Where each is the space-separated list of one or more + // package names: + // + // [ ...] + // + // All the packages in the group should belong to the same "logical + // package", such as -devel, -doc, -common packages. They normally have the + // same version. + // + // The first group is called the main group and the first package in the + // group is called the main package. Note that all the groups are consumed + // (installed) but only the main group is produced (packaged). + // + // (Note that above we use the term "logical package" instead of "base + // package" since the main package may not be the base package, for example + // being the -libs subpackage.) + // + // We allow/recommend specifying the -devel package instead of the main + // package for libraries (the bpkg package name starts with lib), seeing + // that we are capable of detecting the main package automatically. If the + // library name happens to end with -devel (which poses an ambiguity), then + // the -devel package should be specified explicitly as the second package + // to disambiguate this situation (if a non-library name happened to start + // with lib and end with -devel, well, you are out of luck, I guess). + // + // Note also that for now we treat all the packages from the non-main groups + // as extras but in the future we may decide to sort them out like the main + // group (see parse_name_value() for details). + // + // The Fedora package version has the [:]- form + // where the parts correspond to the Epoch (optional upstream versioning + // scheme), Version (upstream version), and Release (Fedora's package + // revision) RPM tags (see the Fedora Package Versioning Guidelines and RPM + // tags documentation for details). If no explicit mapping to the bpkg + // version is specified with the fedora-to-downstream-version (or alike) + // manifest values or none match, then we fallback to using the + // part as the bpkg version. If explicit mapping is specified, then we match + // it against the [:] parts ignoring . + // + struct system_package_status_fedora: system_package_status + { + string main; + string devel; + string static_; + string doc; + string debuginfo; + string debugsource; + string common; + strings extras; + + string fallback; // Fallback for main/devel package based on project name. + + // The `dnf list` output. + // + struct package_info + { + string name; + string installed_version; // Empty if none. + string candidate_version; // Empty if none and no installed_version. + + // The architecture of the installed/candidate package version. Can only + // be the host architecture or noarch (so it could have been bool but + // it's more convenient to have the actual name). + // + // Note that in Fedora the same package version can be available for + // multiple architectures or be architecture-independent. For example: + // + // dbus-libs-1:1.12.22-1.fc35.i686 + // dbus-libs-1:1.12.22-1.fc35.x86_64 + // dbus-common-1:1.12.22-1.fc35.noarch + // code-insiders-1.75.0-1675123170.el7.armv7hl + // code-insiders-1.75.0-1675123170.el7.aarch64 + // code-insiders-1.75.0-1675123170.el7.x86_64 + // + // Thus, for a package query we normally need to qualify the package + // with the architecture suffix or filter the query result, normally + // skipping packages for architectures other than the host architecture. + // + string installed_arch; + string candidate_arch; + + explicit + package_info (string n): name (move (n)) {} + + bool + unknown () const + { + return installed_version.empty () && candidate_version.empty (); + } + + bool + known () const {return !unknown ();} + }; + + vector package_infos; + size_t package_infos_main = 0; // Size of the main group. + + explicit + system_package_status_fedora (string m, string d = {}, string f = {}) + : main (move (m)), devel (move (d)), fallback (move (f)) + { + assert (!main.empty () || !devel.empty ()); + } + + system_package_status_fedora () = default; + }; + + class system_package_manager_fedora: public system_package_manager + { + public: + virtual optional + pkg_status (const package_name&, const available_packages*) override; + + virtual void + pkg_install (const vector&) override; + + public: + // Expects os_release::name_id to be "fedora" or os_release::like_ids to + // contain "fedora". + using system_package_manager::system_package_manager; + + // Implementation details exposed for testing (see definitions for + // documentation). + // + public: + using package_status = system_package_status_fedora; + using package_info = package_status::package_info; + + void + dnf_list (vector&, size_t = 0); + + vector> + dnf_repoquery_requires (const string&, const string&, const string&); + + void + dnf_makecache (); + + void + dnf_install (const strings&); + + pair + dnf_common (const char*); + + static package_status + parse_name_value (const package_name&, const string&, bool, bool, bool); + + static string + main_from_devel (const string&, + const string&, + const vector>&); + + // If simulate is not NULL, then instead of executing the actual dnf + // commands simulate their execution: (1) for `dnf list` and `dnf + // repoquery --requires` by printing their command lines and reading the + // results from files specified in the below dnf_* maps and (2) for `dnf + // makecache`, `dnf install`, and `dnf mark install` by printing their + // command lines and failing if requested. + // + // In the (1) case if the corresponding map entry does not exist or the + // path is empty, then act as if the specified package/version is + // unknown. If the path is special "-" then read from stdin. For `dnf + // list` and `dnf repoquery --requires` different post-fetch and (for the + // former) post-install results can be specified (if the result is not + // found in one of the later maps, the previous map is used as a + // fallback). Note that the keys in the dnf_list_* maps are the package + // sets and the corresponding result file is expected to contain (or not) + // the results for all of them. See dnf_list() and + // dnf_repoquery_requires() implementations for details on the expected + // results. + // + struct simulation + { + std::map dnf_list_; + std::map dnf_list_fetched_; + std::map dnf_list_installed_; + + struct package + { + string name; + string version; + string arch; + + bool + operator< (const package& p) const + { + if (int r = name.compare (p.name)) + return r < 0; + + if (int r = version.compare (p.version)) + return r < 0; + + return arch < p.arch; + } + }; + + std::map dnf_repoquery_requires_; + std::map dnf_repoquery_requires_fetched_; + + bool dnf_makecache_fail_ = false; + bool dnf_install_fail_ = false; + bool dnf_mark_install_fail_ = false; + }; + + const simulation* simulate_ = nullptr; + + protected: + bool fetched_ = false; // True if already fetched metadata. + bool installed_ = false; // True if already installed. + + std::map> status_cache_; + }; +} + +#endif // BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX 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); +} diff --git a/bpkg/system-package-manager-fedora.test.testscript b/bpkg/system-package-manager-fedora.test.testscript new file mode 100644 index 0000000..f37b531 --- /dev/null +++ b/bpkg/system-package-manager-fedora.test.testscript @@ -0,0 +1,1310 @@ +# file : bpkg/system-package-manager-fedora.test.testscript +# license : MIT; see accompanying LICENSE file + +: dnf-list +: +{ + test.arguments += dnf-list + + : basics + : + $* openssl-libs openssl-devel openssl1.1 openssl1.1-devel libsigc++40 libcurl lrmi rust-uuid+std-devel <>EOE >>EOO + Installed Packages + libcurl.i686 7.79.1-5.fc35 @updates + libcurl.x86_64 7.79.1-5.fc35 @updates + openssl-devel.x86_64 1:1.1.1q-1.fc35 @updates + openssl-libs.i686 1:1.1.1q-1.fc35 @updates + openssl-libs.x86_64 1:1.1.1q-1.fc35 @updates + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + libcurl.i686 7.79.1-7.fc35 updates + libcurl.x86_64 7.79.1-7.fc35 updates + lrmi.i686 0.10-28.fc35 fedora + openssl-devel.i686 1:1.1.1q-1.fc35 updates + openssl1.1.i686 1:1.1.1i-3.fc35 fedora + openssl1.1.x86_64 1:1.1.1i-3.fc35 fedora + openssl1.1-devel.i686 1:1.1.1i-3.fc35 fedora + openssl1.1-devel.x86_64 1:1.1.1i-3.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + rust-uuid+std-devel.noarch 1.2.1-1.fc35 updates + EOI + LC_ALL=C dnf list --all --cacheonly --quiet openssl-libs openssl-devel openssl1.1 openssl1.1-devel libsigc++40 libcurl lrmi rust-uuid+std-devel rpm <- + EOE + openssl-libs '1:1.1.1q-1.fc35' 'x86_64' '1:1.1.1q-1.fc35' 'x86_64' + openssl-devel '1:1.1.1q-1.fc35' 'x86_64' '1:1.1.1q-1.fc35' 'x86_64' + openssl1.1 '' '' '1:1.1.1i-3.fc35' 'x86_64' + openssl1.1-devel '' '' '1:1.1.1i-3.fc35' 'x86_64' + libsigc++40 '' '' '' '' + libcurl '7.79.1-5.fc35' 'x86_64' '7.79.1-7.fc35' 'x86_64' + lrmi '' '' '' '' + rust-uuid+std-devel '' '' '1.2.1-1.fc35' 'noarch' + EOO + + : unknown + : + $* libsigc++40 <>EOE >>EOO + Installed Packages + rpm.x86_64 4.17.1-3.fc35 updates + Available Packages + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsigc++40 rpm <- + EOE + libsigc++40 '' '' '' '' + EOO + + : non-host-arc + : + $* lrmi <>EOE >>EOO + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + lrmi.i686 0.10-28.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + EOI + LC_ALL=C dnf list --all --cacheonly --quiet lrmi rpm <- + EOE + lrmi '' '' '' '' + EOO + + : dnf + : + $* rpm <>EOE >>EOO + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + LC_ALL=C dnf list --all --cacheonly --quiet rpm rpm <- + EOE + rpm '4.17.1-2.fc35' 'x86_64' '4.17.1-3.fc35' 'x86_64' + EOO + + : dnf-not-exist + : + $* openssl-libs <>EOE != 0 + Installed Packages + openssl-libs.i686 1:1.1.1q-1.fc35 @updates + openssl-libs.x86_64 1:1.1.1q-1.fc35 @updates + EOI + LC_ALL=C dnf list --all --cacheonly --quiet openssl-libs rpm <- + error: rpm package doesn't exist + EOE +} + +: dnf-repoquery-requires +: +{ + test.arguments += dnf-repoquery-requires + + : basics + : + $* openssl-devel '1:1.1.1q-1.fc35' x86_64 <>EOE >>EOO + opae-devel x86_64 0:2.0.0-2.3.fc35 + openssl-devel i686 1:1.1.1q-1.fc35 + openssl-devel x86_64 1:1.1.1q-1.fc35 + openssl-libs x86_64 1:1.1.1q-1.fc35 + openssl1.1 x86_64 1:1.1.1i-3.fc35 + openssl1.1-devel i686 1:1.1.1i-3.fc35 + openssl1.1-devel x86_64 1:1.1.1i-3.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" openssl-devel-1:1.1.1q-1.fc35.x86_64 <- + EOE + opae-devel 2.0.0-2.3.fc35 + openssl-libs 1:1.1.1q-1.fc35 + openssl1.1 1:1.1.1i-3.fc35 + openssl1.1-devel 1:1.1.1i-3.fc35 + pkgconf-pkg-config 1.8.0-1.fc35 + EOO + + : no-arch + : + $* rust-uuid+std-devel 1.2.1-1.fc35 noarch <>EOE >>EOO + cargo x86_64 0:1.65.0-1.fc35 + rust-uuid-devel noarch 0:1.2.1-1.fc35 + EOI + LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" rust-uuid+std-devel-1.2.1-1.fc35.noarch <- + EOE + cargo 1.65.0-1.fc35 + rust-uuid-devel 1.2.1-1.fc35 + EOO + + : no-arch-dependency + : + $* dhcp-client '12:4.4.3-4.P1.fc35' x86_64 <>EOE >>EOO + bash i686 0:5.1.8-3.fc35 + bash x86_64 0:5.1.8-3.fc35 + coreutils x86_64 0:8.32-36.fc35 + coreutils-single x86_64 0:8.32-36.fc35 + dhcp-common noarch 12:4.4.3-4.P1.fc35 + gawk i686 0:5.1.0-4.fc35 + gawk x86_64 0:5.1.0-4.fc35 + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + grep x86_64 0:3.6-4.fc35 + ipcalc x86_64 0:1.0.1-2.fc35 + iproute x86_64 0:5.13.0-2.fc35 + iputils x86_64 0:20210722-1.fc35 + libcap-ng x86_64 0:0.8.2-8.fc35 + sed x86_64 0:4.8-8.fc35 + systemd i686 0:249.13-6.fc35 + systemd x86_64 0:249.13-6.fc35 + EOI + LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" dhcp-client-12:4.4.3-4.P1.fc35.x86_64 <- + EOE + bash 5.1.8-3.fc35 + coreutils 8.32-36.fc35 + coreutils-single 8.32-36.fc35 + dhcp-common 12:4.4.3-4.P1.fc35 + gawk 5.1.0-4.fc35 + glibc 2.34-49.fc35 + grep 3.6-4.fc35 + ipcalc 1.0.1-2.fc35 + iproute 5.13.0-2.fc35 + iputils 20210722-1.fc35 + libcap-ng 0.8.2-8.fc35 + sed 4.8-8.fc35 + systemd 249.13-6.fc35 + EOO + + : no-depends + : + $* glibc 2.34-38.fc35 x86_64 <:'' 2>>EOE >:'' + LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" glibc-2.34-38.fc35.x86_64 <- + EOE + + : unknown + : + $* glibg 2.34-38.fc35 x86_64 <:'' 2>>EOE >:'' + LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" glibg-2.34-38.fc35.x86_64 <- + EOE +} + +: parse-name-value +: +{ + test.arguments += parse-name-value + + : basics + : + $* libmysqlclient <>EOO + community-mysql-libs community-mysql-devel community-mysql-common community-mysql-libs-debuginfo community-mysql-debugsource community-mysql-extras, libstdc++ libstdc++-devel libstdc++-docs libstdc++-static, libz-dev + EOI + main: community-mysql-libs + devel: community-mysql-devel + debuginfo: community-mysql-libs-debuginfo + debugsource: community-mysql-debugsource + common: community-mysql-common + extras: community-mysql-extras libstdc++ libstdc++-devel libstdc++-static libstdc++-docs libz-dev + EOO + + : non-lib + : + $* sqlite3 <>EOO + sqlite sqlite-doc sqlite-analyzer sqlite-tools + EOI + main: sqlite + doc: sqlite-doc + extras: sqlite-analyzer sqlite-tools + EOO + + : lib-devel + : + $* libsqlite3 <>EOO + sqlite-devel + EOI + devel: sqlite-devel + EOO + + : non-lib-devel + : + $* ssl-devel <>EOO + ssl-devel + EOI + main: ssl-devel + EOO + + : lib-custom-devel + : + $* libfoo-devel <>EOO + libfoo-devel libfoo-devel-devel + EOI + main: libfoo-devel + devel: libfoo-devel-devel + EOO +} + +: main-from-devel +: +{ + test.arguments += main-from-devel + + : libs + : + $* sqlite-devel 3.36.0-3.fc35 <'sqlite-libs' + pkgconf-pkg-config 1.8.0-1.fc35 + sqlite 3.36.0-3.fc35 + sqlite-libs 3.36.0-3.fc35 + EOI + + : no-libs + : + $* xerces-c-devel 3.2.3-4.fc35 <'xerces-c' + pkgconf-pkg-config 1.8.0-1.fc35 + xerces-c 3.2.3-4.fc35 + EOI + + : no-dependencies + : + $* boost-http-server-devel 0-1.20220116gitcd5245f.fc35 <:'' >'' +} + +: build +: +{ + test.arguments += build + + : libpq + : + : Note that here we will test without package manifest, auto-creating a stub, + : and thus the -devel package name needs to be deducible from the package + : name (no project name fallback is available). + : + { + : installed + : + cat <=libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq-devel.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + EOI + cat <=libpq-devel.requires; + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + libpq x86_64 0:13.4-1.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=libpq.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + EOI + $* libpq --install libpq <>EOE >>EOO + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + dnf-repoquery-requires: libpq-devel 13.4-1.fc35 x86_64 libpq-devel.requires + dnf-list: libpq libpq.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 fedora + EOI + cat <=libpq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 fedora + EOI + cat <=libpq-devel.requires; + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + libpq x86_64 0:13.4-1.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=libpq.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + EOI + $* libpq --install libpq <>EOE >>EOO + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + dnf-list: libpq-devel libpq-devel.info + dnf-repoquery-requires: libpq-devel 13.4-1.fc35 x86_64 libpq-devel.requires + dnf-list: libpq libpq.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.3-3.fc35 fedora + libpq-devel.x86_64 13.3-3.fc35 fedora + EOI + cat <=libpq-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 fedora + EOI + cat <=libpq-devel.requires-fetched; + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + libpq x86_64 0:13.4-1.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=libpq.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.3-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + libpq.x86_64 13.4-1.fc35 @fedora + EOI + cat <=libpq.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + EOI + $* libpq --install libpq <>EOE >>EOO + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + dnf-list-fetched: libpq-devel libpq-devel.info-fetched + dnf-repoquery-requires: libpq-devel 13.4-1.fc35 x86_64 libpq-devel.requires-fetched + dnf-list-fetched: libpq libpq.info-fetched + dnf-list-installed: libpq libpq.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.3-3.fc35 fedora + libpq-devel.x86_64 13.3-3.fc35 fedora + EOI + cat <=libpq-devel.requires; + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + libpq x86_64 0:13.3-3.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=libpq.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.3-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.3-3.fc35 fedora + EOI + cat <=libpq.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + EOI + $* libpq --install --no-fetch libpq <>EOE >>EOO != 0 + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + dnf-repoquery-requires: libpq-devel 13.3-3.fc35 x86_64 libpq-devel.requires + dnf-list: libpq libpq.info + dnf-list-installed: libpq libpq.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 @fedora + EOI + cat <=libpq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 @fedora + EOI + cat <=libpq-devel.requires; + glibc i686 0:2.34-49.fc35 + glibc x86_64 0:2.34-49.fc35 + libpq x86_64 0:13.4-1.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=libpq.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + libpq.x86_64 13.4-1.fc35 @fedora + EOI + cat <=libpq.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libpq.x86_64 13.4-1.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq.i686 13.4-1.fc35 fedora + EOI + $* libpq --install libpq <>EOE >>EOO + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + dnf-list: libpq-devel libpq-devel.info + dnf-repoquery-requires: libpq-devel 13.4-1.fc35 x86_64 libpq-devel.requires + dnf-list: libpq libpq.info + dnf-list-installed: libpq libpq.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libpq-devel.i686 13.4-1.fc35 fedora + libpq-devel.x86_64 13.4-1.fc35 @fedora + EOI + $* libpq <>EOE != 0 + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libpq --install libpq <>EOE != 0 + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libpq-devel+pq-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libpq --install --no-fetch libpq <>EOE != 0 + dnf-list: libpq-devel pq-devel libpq-devel+pq-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel pq-devel rpm =libsqlite3.manifest + : 1 + name: libsqlite3 + version: 3.39.4+1 + project: sqlite + summary: SQL database engine as an in-process C library + license: blessing ; SQLite Blessing. + EOI + + + : deduce-dev-fail + : + cat <=libsqlite3-devel+sqlite3-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libsqlite3 --install libsqlite3 <>EOE != 0 + dnf-list: libsqlite3-devel sqlite3-devel libsqlite3-devel+sqlite3-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsqlite3-devel sqlite3-devel rpm =libsqlite3-devel+sqlite-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + sqlite-devel.x86_64 3.36.0-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite-devel.i686 3.36.0-3.fc35 fedora + EOI + cat <=sqlite-devel.requires; + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + sqlite x86_64 0:3.36.0-3.fc35 + sqlite-libs x86_64 0:3.36.0-3.fc35 + EOI + cat <=sqlite-libs.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + sqlite-libs.i686 3.36.0-3.fc35 @fedora + sqlite-libs.x86_64 3.36.0-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libsqlite3 --install libsqlite3 <>EOE >>EOO + manifest: libsqlite3 libsqlite3.manifest + + dnf-list: libsqlite3-devel sqlite-devel libsqlite3-devel+sqlite-devel.info + dnf-repoquery-requires: sqlite-devel 3.36.0-3.fc35 x86_64 sqlite-devel.requires + dnf-list: sqlite-libs sqlite-libs.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsqlite3-devel sqlite-devel rpm =libsqlite3-devel+sqlite-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite-devel.i686 3.36.0-3.fc35 fedora + sqlite-devel.x86_64 3.36.0-3.fc35 @fedora + EOI + cat <=sqlite-devel.requires; + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + sqlite x86_64 0:3.36.0-3.fc35 + sqlite-libs x86_64 0:3.36.0-3.fc35 + EOI + cat <=sqlite-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite-devel.i686 3.36.0-3.fc35 fedora + sqlite-devel.x86_64 3.36.0-3.fc35 @fedora + EOI + cat <=sqlite-libs.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite-libs.i686 3.36.0-3.fc35 @fedora + sqlite-libs.x86_64 3.36.0-3.fc35 @fedora + EOI + cat <=sqlite-libs.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + sqlite-libs.x86_64 3.36.0-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite-libs.i686 3.36.0-3.fc35 @fedora + EOI + $* libsqlite3 --install libsqlite3 <>EOE >>EOO + manifest: libsqlite3 libsqlite3.manifest + + dnf-list: libsqlite3-devel sqlite-devel libsqlite3-devel+sqlite-devel.info + dnf-repoquery-requires: sqlite-devel 3.36.0-3.fc35 x86_64 sqlite-devel.requires + dnf-list: sqlite-devel sqlite-devel.info-fetched + dnf-list-fetched: sqlite-libs sqlite-libs.info-fetched + dnf-list-installed: sqlite-libs sqlite-libs.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsqlite3-devel sqlite-devel rpm =sqlite3.manifest + : 1 + name: sqlite3 + version: 3.39.4+1 + project: sqlite + summary: SQLite database engine shell program + license: blessing ; SQLite Blessing. + EOI + + + : deduce-main-fail + : + cat <=sqlite3.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* sqlite3 --install sqlite3 <>EOE != 0 + dnf-list: sqlite3 sqlite3.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet sqlite3 rpm =sqlite3+sqlite.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + sqlite.x86_64 3.36.0-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite.i686 3.36.0-3.fc35 fedora + EOI + ln -s sqlite3+sqlite.info sqlite.info; + $* sqlite3 --install sqlite3 <>EOE >>EOO + manifest: sqlite3 sqlite3.manifest + + dnf-list: sqlite3 sqlite sqlite3+sqlite.info + dnf-list: sqlite sqlite.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet sqlite3 sqlite rpm =sqlite3+sqlite.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite.i686 3.35.0-1.fc35 fedora + sqlite.x86_64 3.35.0-1.fc35 @fedora + EOI + cat <=sqlite.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite.i686 3.36.0-3.fc35 fedora + sqlite.x86_64 3.36.0-3.fc35 @fedora + EOI + cat <=sqlite.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + sqlite.x86_64 3.36.0-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + sqlite.i686 3.36.0-3.fc35 fedora + EOI + $* sqlite3 --install sqlite3 <>EOE >>EOO + manifest: sqlite3 sqlite3.manifest + + dnf-list: sqlite3 sqlite sqlite3+sqlite.info + dnf-list-fetched: sqlite sqlite.info-fetched + dnf-list-installed: sqlite sqlite.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet sqlite3 sqlite rpm =libncurses.manifest + : 1 + name: libncurses + version: 6.4 + upstream-version: 6.4.0 + project: ncurses + fedora-to-downstream-version: /([0-9]+)\.([0-9]+)/\1.\2.0/ + summary: ncurses C library + license: MIT + EOI + +cat <=libncurses-c++.manifest + : 1 + name: libncurses-c++ + version: 6.4 + upstream-version: 6.4.0 + project: ncurses + fedora-name: ncurses-c++-libs ncurses-devel + fedora-to-downstream-version: /([0-9]+)\.([0-9]+)/\1.\2.0/ + summary: ncurses C++ library + license: MIT + EOI + + + : installed + : + ln -s ../libncurses.manifest ./; + ln -s ../libncurses-c++.manifest ./; + cat <=libncurses-devel+ncurses-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-devel.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-devel.requires; + bash i686 0:5.1.8-3.fc35 + bash x86_64 0:5.1.8-3.fc35 + ncurses-c++-libs x86_64 0:6.2-8.20210508.fc35 + ncurses-devel i686 0:6.2-8.20210508.fc35 + ncurses-devel x86_64 0:6.2-8.20210508.fc35 + ncurses-libs x86_64 0:6.2-8.20210508.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=ncurses-libs.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + cat <=ncurses-c++-libs+ncurses-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-libs+ncurses-c++-libs.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + EOI + $* libncurses libncurses-c++ --install libncurses libncurses-c++ <>EOE >>EOO + manifest: libncurses libncurses.manifest + manifest: libncurses-c++ libncurses-c++.manifest + + dnf-list: libncurses-devel ncurses-devel libncurses-devel+ncurses-devel.info + dnf-repoquery-requires: ncurses-devel 6.2-8.20210508.fc35 x86_64 ncurses-devel.requires + dnf-list: ncurses-libs ncurses-libs.info + dnf-list: ncurses-c++-libs ncurses-devel ncurses-c++-libs+ncurses-devel.info + dnf-list-installed: ncurses-libs ncurses-c++-libs ncurses-libs+ncurses-c++-libs.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libncurses-devel ncurses-devel rpm =libncurses-devel+ncurses-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 @fedora + EOI + cat <=ncurses-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-devel.requires-fetched; + bash i686 0:5.1.8-3.fc35 + bash x86_64 0:5.1.8-3.fc35 + ncurses-c++-libs x86_64 0:6.2-8.20210508.fc35 + ncurses-devel i686 0:6.2-8.20210508.fc35 + ncurses-devel x86_64 0:6.2-8.20210508.fc35 + ncurses-libs x86_64 0:6.2-8.20210508.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=ncurses-libs.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + EOI + cat <=ncurses-c++-libs+ncurses-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-libs+ncurses-c++-libs.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + EOI + $* libncurses libncurses-c++ --install libncurses libncurses-c++ <>EOE >>EOO + manifest: libncurses libncurses.manifest + manifest: libncurses-c++ libncurses-c++.manifest + + dnf-list: libncurses-devel ncurses-devel libncurses-devel+ncurses-devel.info + dnf-list-fetched: ncurses-devel ncurses-devel.info-fetched + dnf-repoquery-requires-fetched: ncurses-devel 6.2-8.20210508.fc35 x86_64 ncurses-devel.requires-fetched + dnf-list-fetched: ncurses-libs ncurses-libs.info-fetched + dnf-list-fetched: ncurses-c++-libs ncurses-devel ncurses-c++-libs+ncurses-devel.info-fetched + dnf-list-installed: ncurses-libs ncurses-c++-libs ncurses-libs+ncurses-c++-libs.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libncurses-devel ncurses-devel rpm =libncurses-devel+ncurses-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 @fedora + EOI + cat <=ncurses-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-devel.requires-fetched; + bash i686 0:5.1.8-3.fc35 + bash x86_64 0:5.1.8-3.fc35 + ncurses-c++-libs x86_64 0:6.2-8.20210508.fc35 + ncurses-devel i686 0:6.2-8.20210508.fc35 + ncurses-devel x86_64 0:6.2-8.20210508.fc35 + ncurses-libs x86_64 0:6.2-8.20210508.fc35 + pkgconf-pkg-config i686 0:1.8.0-1.fc35 + pkgconf-pkg-config x86_64 0:1.8.0-1.fc35 + EOI + cat <=ncurses-libs.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + EOI + cat <=ncurses-c++-libs+ncurses-devel.info-fetched; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + ncurses-devel.i686 6.2-8.20210508.fc35 fedora + ncurses-devel.x86_64 6.2-8.20210508.fc35 fedora + EOI + cat <=ncurses-libs+ncurses-c++-libs.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + ncurses-c++-libs.x86_64 6.2-8.20210508.fc35 @fedora + ncurses-libs.i686 6.2-8.20210508.fc35 @fedora + ncurses-libs.x86_64 6.2-8.20210508.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + ncurses-c++-libs.i686 6.2-8.20210508.fc35 fedora + EOI + $* libncurses libncurses-c++ --install libncurses libncurses-c++ <>EOE >>EOO + manifest: libncurses libncurses.manifest + manifest: libncurses-c++ libncurses-c++.manifest + + dnf-list: libncurses-devel ncurses-devel libncurses-devel+ncurses-devel.info + dnf-list-fetched: ncurses-devel ncurses-devel.info-fetched + dnf-repoquery-requires-fetched: ncurses-devel 6.2-8.20210508.fc35 x86_64 ncurses-devel.requires-fetched + dnf-list-fetched: ncurses-libs ncurses-libs.info-fetched + dnf-list-fetched: ncurses-c++-libs ncurses-devel ncurses-c++-libs+ncurses-devel.info-fetched + dnf-list-installed: ncurses-libs ncurses-c++-libs ncurses-libs+ncurses-c++-libs.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libncurses-devel ncurses-devel rpm =libsigc++.manifest + : 1 + name: libsigc++ + version: 3.4.0 + fedora-name: libsigc++30 libsigc++30-devel libsigc++30-doc + fedora-name: libsigc++20 libsigc++20-devel libsigc++20-doc + summary: Typesafe callback system for standard C++ + license: LGPL-3.0-only + EOI + + + : one-full-installed + : + ln -s ../libsigc++.manifest ./; + cat <=libsigc++30+libsigc++30-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libsigc++30.x86_64 3.0.7-2.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++30.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.x86_64 3.0.7-2.fc35 fedora + EOI + cat <=libsigc++20+libsigc++20-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + libsigc++20-devel.x86_64 2.10.7-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++20.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.i686 2.10.7-3.fc35 fedora + EOI + cat <=libsigc++20.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++20.i686 2.10.7-3.fc35 fedora + EOI + $* libsigc++ --install libsigc++ <>EOE >>EOO + manifest: libsigc++ libsigc++.manifest + + dnf-list: libsigc++30 libsigc++30-devel libsigc++30+libsigc++30-devel.info + dnf-list: libsigc++20 libsigc++20-devel libsigc++20+libsigc++20-devel.info + dnf-list-installed: libsigc++20 libsigc++20.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm =libsigc++30+libsigc++30-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++30.i686 3.0.7-2.fc35 fedora + libsigc++30.x86_64 3.0.7-2.fc35 fedora + libsigc++30-devel.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.x86_64 3.0.7-2.fc35 fedora + EOI + cat <=libsigc++20+libsigc++20-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++20.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.x86_64 2.10.7-3.fc35 fedora + EOI + cat <=libsigc++20.info-installed; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + Available Packages + rpm.x86_64 4.17.1-3.fc35 updates + libsigc++20.i686 2.10.7-3.fc35 fedora + EOI + $* libsigc++ --install libsigc++ <>EOE >>EOO + manifest: libsigc++ libsigc++.manifest + + dnf-list: libsigc++30 libsigc++30-devel libsigc++30+libsigc++30-devel.info + dnf-list: libsigc++20 libsigc++20-devel libsigc++20+libsigc++20-devel.info + dnf-list-installed: libsigc++20 libsigc++20.info-installed + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm =libsigc++30+libsigc++30-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + libsigc++30.i686 3.0.7-2.fc35 fedora + libsigc++30.x86_64 3.0.7-2.fc35 fedora + libsigc++30-devel.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.x86_64 3.0.7-2.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + EOI + cat <=libsigc++20+libsigc++20-devel.info; + Installed Packages + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + libsigc++20.i686 2.10.7-3.fc35 fedora + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + libsigc++20-devel.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.x86_64 2.10.7-3.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libsigc++ --install libsigc++ <>EOE != 0 + manifest: libsigc++ libsigc++.manifest + + dnf-list: libsigc++30 libsigc++30-devel libsigc++30+libsigc++30-devel.info + dnf-list: libsigc++20 libsigc++20-devel libsigc++20+libsigc++20-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm =libsigc++30+libsigc++30-devel.info; + Installed Packages + libsigc++30.x86_64 3.0.7-2.fc35 fedora + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + libsigc++30.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.i686 3.0.7-2.fc35 fedora + libsigc++30-devel.x86_64 3.0.7-2.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + EOI + cat <=libsigc++20+libsigc++20-devel.info; + Installed Packages + libsigc++20.x86_64 2.10.7-3.fc35 @fedora + rpm.x86_64 4.17.1-2.fc35 @updates + Available Packages + libsigc++20.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.i686 2.10.7-3.fc35 fedora + libsigc++20-devel.x86_64 2.10.7-3.fc35 fedora + rpm.x86_64 4.17.1-3.fc35 updates + EOI + $* libsigc++ --install libsigc++ <>EOE != 0 + manifest: libsigc++ libsigc++.manifest + + dnf-list: libsigc++30 libsigc++30-devel libsigc++30+libsigc++30-devel.info + dnf-list: libsigc++20 libsigc++20-devel libsigc++20+libsigc++20-devel.info + EOI + LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm #include +#include using namespace std; using namespace butl; @@ -71,6 +72,25 @@ namespace bpkg r.reset (new system_package_manager_debian ( move (*osr), host, install, fetch, progress, yes, sudo)); } + else if (is_or_like ("fedora") || + is_or_like ("rhel") || + is_or_like ("centos") || + is_or_like ("rocky") || + is_or_like ("almalinux")) + { + if (!name.empty () && name != "fedora") + fail << "unsupported package manager '" << name << "' for " + << osr->name_id << " host"; + + // If we recognized this as Fedora-like in an ad hoc manner, then + // add fedora to like_ids. + // + if (osr->name_id != "fedora" && !is_or_like ("fedora")) + osr->like_ids.push_back ("fedora"); + + r.reset (new system_package_manager_fedora ( + move (*osr), host, install, fetch, progress, yes, sudo)); + } } } diff --git a/doc/manual.cli b/doc/manual.cli index 614bd3c..1f540d2 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -3041,7 +3041,8 @@ libsigc++30 libsigc++30-devel libsigc++30-doc icu libicu libicu-devel libicu-doc -openssl openssl-libs openssl-devel +openssl openssl-libs openssl-devel openssl-static +openssl1.1 openssl1.1-devel curl libcurl libcurl-devel @@ -3049,6 +3050,10 @@ sqlite sqlite-libs sqlite-devel sqlite-doc community-mysql community-mysql-libs community-mysql-devel community-mysql-common community-mysql-server + +ncurses ncurses-libs ncurses-c++-libs ncurses-devel ncurses-static + +keyutils keyutils-libs keyutils-libs-devel \ Based on that, our approach when trying to automatically map a \c{bpkg} -- cgit v1.1