aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2023-02-01 13:47:47 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2023-02-08 15:44:57 +0300
commitd6500b9d7ee5cf68a7507f9d4d726ffb767d827a (patch)
tree6d2af81b0424223b725e4a72973a1488caad7151
parenta05076ceb13341780ea1f446626658164f35c5f3 (diff)
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.
-rw-r--r--bpkg/system-package-manager-debian.cxx5
-rw-r--r--bpkg/system-package-manager-fedora.cxx1665
-rw-r--r--bpkg/system-package-manager-fedora.hxx305
-rw-r--r--bpkg/system-package-manager-fedora.test.cxx382
-rw-r--r--bpkg/system-package-manager-fedora.test.testscript1310
-rw-r--r--bpkg/system-package-manager.cxx20
-rw-r--r--doc/manual.cli7
7 files changed, 3691 insertions, 3 deletions
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 <bpkg/system-package-manager-fedora.hxx>
+
+#include <bpkg/diagnostics.hxx>
+
+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<pair<string, string>>& depends)
+ {
+ // For the main package we first look for a dependency with the
+ // <devel-stem>-libs name and the devel_ver version. Failed that, we try
+ // just <devel-stem>.
+ //
+ // 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<string, string>& 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<package_info>& 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: <timestamp>' 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 <pkg1> <pkg2> ...` is the 2 groups of lines
+ // in the following form:
+ //
+ // Installed Packages
+ // <pkg1>.<arch1> 13.0.0-3.fc35 @<repo1>
+ // <pkg2>.<arch2> 69.1-6.fc35 @<repo2>
+ // Available Packages
+ // <pkg1>.<arch1> 13.0.1-1.fc35 <repo1>
+ // <pkg3>.<arch3> 1.2.11-32.fc35 <repo3>
+ //
+ // 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<bool> 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<pair<string, string>> 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: <timestamp>' 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<pair<string, string>> 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 `<name> <arc> <version>` 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<cstrings, const process_path&> 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<cstrings, const process_path&> (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<cstrings, const process_path&> 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<cstrings, const process_path&> 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<cstrings, const process_path&> 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<const system_package_status*> 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<package_status> 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<available_package>& 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<pair<string, string>> 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<package_info>& pis, size_t main)
+ -> optional<status_type>
+ {
+ 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<package_info>& 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<package_status> r;
+
+ {
+ diag_record dr; // Ambiguity diagnostics.
+
+ for (package_status& ps: candidates)
+ {
+ vector<package_info>& 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<status_type> 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<package_info>& 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<status_type> 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 ([<epoch>:]<version>-<release>).
+ //
+ // 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<version> 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<package_name>& 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<package> 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` <pkg>[-<ver>] 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<package_info> 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 <map>
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/system-package-manager.hxx>
+
+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:
+ //
+ // <package-group> [, <package-group>...]
+ //
+ // Where each <package-group> is the space-separated list of one or more
+ // package names:
+ //
+ // <package-name> [ <package-name>...]
+ //
+ // 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 [<epoch>:]<version>-<release> 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 <version>
+ // part as the bpkg version. If explicit mapping is specified, then we match
+ // it against the [<epoch>:]<version> parts ignoring <release>.
+ //
+ 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_info> 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<const system_package_status*>
+ pkg_status (const package_name&, const available_packages*) override;
+
+ virtual void
+ pkg_install (const vector<package_name>&) 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<package_info>&, size_t = 0);
+
+ vector<pair<string, string>>
+ dnf_repoquery_requires (const string&, const string&, const string&);
+
+ void
+ dnf_makecache ();
+
+ void
+ dnf_install (const strings&);
+
+ pair<cstrings, const process_path&>
+ 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<pair<string, string>>&);
+
+ // 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<strings, path> dnf_list_;
+ std::map<strings, path> dnf_list_fetched_;
+ std::map<strings, path> 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<package, path> dnf_repoquery_requires_;
+ std::map<package, path> 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<package_name, optional<system_package_status_fedora>> 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 <bpkg/system-package-manager-fedora.hxx>
+
+#include <map>
+#include <iostream>
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+#include <bpkg/system-package-manager.test.hxx>
+
+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] <command> ...
+ //
+ // Where <command> is one of:
+ //
+ // dnf-list <pkg>... result comes from stdin
+ //
+ // dnf-repoquery-requires <pkg> <ver> <arc> result comes from stdin
+ //
+ // parse-name-value <pkg> fedora-name value from stdin
+ //
+ // main-from-devel <dev-pkg> <dev-ver> depends comes from stdin in
+ // the `<dep-pkg> <dep-ver>`
+ // per line form
+ //
+ // build <query-pkg>... [--install [--no-fetch] <install-pkg>...]
+ //
+ // 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: <query-pkg> <file>
+ //
+ // Available package manifest for one of <query-pkg>. If none is
+ // specified, then a stub is automatically added.
+ //
+ // dnf-list[-{fetched,installed}]: <sys-pkg>... <file>
+ //
+ // Values for simulation::dnf_list_*. If <file> is the special `!` value,
+ // then make the entry empty.
+ //
+ // dnf-repoquery-requires[-fetched]: <sys-pkg> <sys-ver> <sys-arch> <file>
+ //
+ // Values for simulation::dnf_repoquery_requires_*. If <file> 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); // <command>
+
+ 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); // <pkg>...
+
+ strings key;
+ vector<package_info_> 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); // <pkg> <ver> <arch>
+
+ 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<string, string>& 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); // <pkg>
+
+ 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); // <dev-pkg> <dev-ver>
+
+ string n (argv[2]);
+ string v (argv[3]);
+ vector<pair<string, string>> 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); // <query-pkg>...
+
+ strings qps;
+ map<string, available_packages> aps;
+
+ // Parse <query-pkg>...
+ //
+ 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<strings, path>* 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<package, path>* 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<const string, available_packages>& 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); // <install-pkg>...
+
+ vector<package_name> 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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI 2>>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 <<EOI >>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 <<EOI >>EOO
+ sqlite sqlite-doc sqlite-analyzer sqlite-tools
+ EOI
+ main: sqlite
+ doc: sqlite-doc
+ extras: sqlite-analyzer sqlite-tools
+ EOO
+
+ : lib-devel
+ :
+ $* libsqlite3 <<EOI >>EOO
+ sqlite-devel
+ EOI
+ devel: sqlite-devel
+ EOO
+
+ : non-lib-devel
+ :
+ $* ssl-devel <<EOI >>EOO
+ ssl-devel
+ EOI
+ main: ssl-devel
+ EOO
+
+ : lib-custom-devel
+ :
+ $* libfoo-devel <<EOI >>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 <<EOI >'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 <<EOI >'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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" libpq-devel-13.4-1.fc35.x86_64 <libpq-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ sudo dnf install --quiet --assumeno libpq-13.4-1.fc35 libpq-devel-13.4-1.fc35
+ sudo dnf mark --quiet --assumeno install --cacheonly libpq-13.4-1.fc35 libpq-devel-13.4-1.fc35
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ EOE
+ libpq 13.4 (libpq 13.4-1.fc35) installed
+ EOO
+
+
+ : part-installed
+ :
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel rpm <libpq-devel.info
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" libpq-devel-13.4-1.fc35.x86_64 <libpq-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ sudo dnf install --quiet --assumeno libpq libpq-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly libpq libpq-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ EOE
+ libpq 13.4 (libpq 13.4-1.fc35) part installed
+ EOO
+
+
+ : part-installed-upgrade
+ :
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel rpm <libpq-devel.info-fetched
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" libpq-devel-13.4-1.fc35.x86_64 <libpq-devel.requires-fetched
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info-fetched
+ sudo dnf install --quiet --assumeno libpq libpq-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly libpq libpq-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info-installed
+ EOE
+ libpq 13.4 (libpq 13.4-1.fc35) part installed
+ EOO
+
+
+ # Note that the semantics is unrealistic (maybe background metadata update
+ # happened right before installing libpq). Also note that in contrast to
+ # the part-installed-upgrade test we operate in the --sys-no-fetch mode.
+ #
+ : part-installed-upgrade-version-change
+ :
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" libpq-devel-13.3-3.fc35.x86_64 <libpq-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ sudo dnf install --quiet --assumeno libpq libpq-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly libpq libpq-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info-installed
+ error: unexpected fedora package version for libpq
+ info: expected: 13.3-3.fc35
+ info: installed: 13.4-1.fc35
+ info: consider retrying the bpkg command
+ EOE
+ libpq 13.3 (libpq 13.3-3.fc35) part installed
+ EOO
+
+
+ : not-installed
+ :
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq-devel rpm <libpq-devel.info
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" libpq-devel-13.4-1.fc35.x86_64 <libpq-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info
+ sudo dnf install --quiet --assumeno libpq libpq-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly libpq libpq-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet libpq rpm <libpq.info-installed
+ EOE
+ libpq 13.4 (libpq 13.4-1.fc35) not installed
+ EOO
+
+
+ : no-install
+ :
+ cat <<EOI >=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 <<EOI 2>>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
+ error: no installed system package for libpq
+ EOE
+
+
+ : not-available
+ :
+ cat <<EOI >=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 <<EOI 2>>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
+ error: unable to guess devel fedora package for libpq
+ info: neither libpq-devel nor pq-devel fedora package exists
+ info: consider specifying explicit mapping in libpq package manifest
+ EOE
+
+
+ : not-available-no-fetch
+ :
+ cat <<EOI >=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 <<EOI 2>>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
+ error: unable to guess devel fedora package for libpq
+ info: neither libpq-devel nor pq-devel fedora package exists
+ info: consider specifying explicit mapping in libpq package manifest
+ EOE
+ }
+
+ : libsqlite3
+ :
+ {
+ +cat <<EOI >=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 <<EOI >=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 <<EOI 2>>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+sqlite3-devel.info
+ error: unable to guess devel fedora package for libsqlite3
+ info: neither libsqlite3-devel nor sqlite3-devel fedora package exists
+ info: consider specifying explicit mapping in libsqlite3 package manifest
+ EOE
+
+
+ : installed
+ :
+ : In particular test the project name-based deduction of the -devel
+ : package.
+ :
+ ln -s ../libsqlite3.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" sqlite-devel-3.36.0-3.fc35.x86_64 <sqlite-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite-libs rpm <sqlite-libs.info
+ sudo dnf install --quiet --assumeno sqlite-libs-3.36.0-3.fc35 sqlite-devel-3.36.0-3.fc35
+ sudo dnf mark --quiet --assumeno install --cacheonly sqlite-libs-3.36.0-3.fc35 sqlite-devel-3.36.0-3.fc35
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite-libs rpm <sqlite-libs.info
+ EOE
+ libsqlite3 3.36.0 (sqlite-libs 3.36.0-3.fc35) installed
+ EOO
+
+
+ : not-installed
+ :
+ ln -s ../libsqlite3.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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 <libsqlite3-devel+sqlite-devel.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite-devel rpm <sqlite-devel.info-fetched
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" sqlite-devel-3.36.0-3.fc35.x86_64 <sqlite-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite-libs rpm <sqlite-libs.info-fetched
+ sudo dnf install --quiet --assumeno sqlite-libs sqlite-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly sqlite-libs sqlite-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite-libs rpm <sqlite-libs.info-installed
+ EOE
+ libsqlite3 3.36.0 (sqlite-libs 3.36.0-3.fc35) not installed
+ EOO
+ }
+
+ : sqlite3
+ :
+ {
+ +cat <<EOI >=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 <<EOI >=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 <<EOI 2>>EOE != 0
+ dnf-list: sqlite3 sqlite3.info
+ EOI
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite3 rpm <sqlite3.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite3 rpm <sqlite3.info
+ error: no installed or available system package for sqlite3
+ EOE
+
+
+ : installed
+ :
+ : In particular test the project name-based deduction of the main
+ : package.
+ :
+ ln -s ../sqlite3.manifest ./;
+ cat <<EOI >=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 <<EOI 2>>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
+ sudo dnf install --quiet --assumeno sqlite-3.36.0-3.fc35
+ sudo dnf mark --quiet --assumeno install --cacheonly sqlite-3.36.0-3.fc35
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite rpm <sqlite.info
+ EOE
+ sqlite3 3.36.0 (sqlite 3.36.0-3.fc35) installed
+ EOO
+
+
+ : not-installed
+ :
+ ln -s ../sqlite3.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI 2>>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 <sqlite3+sqlite.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite rpm <sqlite.info-fetched
+ sudo dnf install --quiet --assumeno sqlite
+ sudo dnf mark --quiet --assumeno install --cacheonly sqlite
+ LC_ALL=C dnf list --all --cacheonly --quiet sqlite rpm <sqlite.info-installed
+ EOE
+ sqlite3 3.36.0 (sqlite 3.36.0-3.fc35) not installed
+ EOO
+ }
+
+ : libncurses
+ :
+ {
+ +cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" ncurses-devel-6.2-8.20210508.fc35.x86_64 <ncurses-devel.requires
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs rpm <ncurses-libs.info
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-c++-libs ncurses-devel rpm <ncurses-c++-libs+ncurses-devel.info
+ sudo dnf install --quiet --assumeno ncurses-libs-6.2-8.20210508.fc35 ncurses-devel-6.2-8.20210508.fc35 ncurses-c++-libs-6.2-8.20210508.fc35
+ sudo dnf mark --quiet --assumeno install --cacheonly ncurses-libs-6.2-8.20210508.fc35 ncurses-devel-6.2-8.20210508.fc35 ncurses-c++-libs-6.2-8.20210508.fc35
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs ncurses-c++-libs rpm <ncurses-libs+ncurses-c++-libs.info-installed
+ EOE
+ libncurses 6.2.0 (ncurses-libs 6.2-8.20210508.fc35) installed
+ libncurses-c++ 6.2.0 (ncurses-c++-libs 6.2-8.20210508.fc35) installed
+ EOO
+
+
+ : part-installed
+ :
+ ln -s ../libncurses.manifest ./;
+ ln -s ../libncurses-c++.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-devel rpm <ncurses-devel.info-fetched
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" ncurses-devel-6.2-8.20210508.fc35.x86_64 <ncurses-devel.requires-fetched
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs rpm <ncurses-libs.info-fetched
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-c++-libs ncurses-devel rpm <ncurses-c++-libs+ncurses-devel.info-fetched
+ sudo dnf install --quiet --assumeno ncurses-libs ncurses-devel ncurses-c++-libs
+ sudo dnf mark --quiet --assumeno install --cacheonly ncurses-libs ncurses-devel ncurses-c++-libs
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs ncurses-c++-libs rpm <ncurses-libs+ncurses-c++-libs.info-installed
+ EOE
+ libncurses 6.2.0 (ncurses-libs 6.2-8.20210508.fc35) part installed
+ libncurses-c++ 6.2.0 (ncurses-c++-libs 6.2-8.20210508.fc35) part installed
+ EOO
+
+
+ : not-installed
+ :
+ ln -s ../libncurses.manifest ./;
+ ln -s ../libncurses-c++.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-devel rpm <ncurses-devel.info-fetched
+ LC_ALL=C dnf repoquery --requires --quiet --cacheonly --resolve --qf "%{name} %{arch} %{epoch}:%{version}-%{release}" ncurses-devel-6.2-8.20210508.fc35.x86_64 <ncurses-devel.requires-fetched
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs rpm <ncurses-libs.info-fetched
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-c++-libs ncurses-devel rpm <ncurses-c++-libs+ncurses-devel.info-fetched
+ sudo dnf install --quiet --assumeno ncurses-libs ncurses-devel ncurses-c++-libs
+ sudo dnf mark --quiet --assumeno install --cacheonly ncurses-libs ncurses-devel ncurses-c++-libs
+ LC_ALL=C dnf list --all --cacheonly --quiet ncurses-libs ncurses-c++-libs rpm <ncurses-libs+ncurses-c++-libs.info-installed
+ EOE
+ libncurses 6.2.0 (ncurses-libs 6.2-8.20210508.fc35) not installed
+ libncurses-c++ 6.2.0 (ncurses-c++-libs 6.2-8.20210508.fc35) not installed
+ EOO
+ }
+
+ : libsigc++
+ :
+ {
+ +cat <<EOI >=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 <<EOI >=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 <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ sudo dnf install --quiet --assumeno libsigc++20-2.10.7-3.fc35 libsigc++20-devel-2.10.7-3.fc35
+ sudo dnf mark --quiet --assumeno install --cacheonly libsigc++20-2.10.7-3.fc35 libsigc++20-devel-2.10.7-3.fc35
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 rpm <libsigc++20.info-installed
+ EOE
+ libsigc++ 2.10.7 (libsigc++20 2.10.7-3.fc35) installed
+ EOO
+
+
+ : one-part-installed
+ :
+ ln -s ../libsigc++.manifest ./;
+ cat <<EOI >=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 <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm <libsigc++30+libsigc++30-devel.info
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ sudo dnf install --quiet --assumeno libsigc++20 libsigc++20-devel
+ sudo dnf mark --quiet --assumeno install --cacheonly libsigc++20 libsigc++20-devel
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 rpm <libsigc++20.info-installed
+ EOE
+ libsigc++ 2.10.7 (libsigc++20 2.10.7-3.fc35) part installed
+ EOO
+
+
+ : none-installed
+ :
+ ln -s ../libsigc++.manifest ./;
+ cat <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm <libsigc++30+libsigc++30-devel.info
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ error: multiple available fedora packages for libsigc++
+ info: candidate: libsigc++30 3.0.7-2.fc35
+ info: candidate: libsigc++20 2.10.7-3.fc35
+ info: consider installing the desired package manually and retrying the bpkg command
+ EOE
+
+
+ : both-part-installed
+ :
+ ln -s ../libsigc++.manifest ./;
+ cat <<EOI >=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 <<EOI >=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++ <<EOI 2>>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
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ sudo dnf makecache --quiet --assumeno --refresh
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++30 libsigc++30-devel rpm <libsigc++30+libsigc++30-devel.info
+ LC_ALL=C dnf list --all --cacheonly --quiet libsigc++20 libsigc++20-devel rpm <libsigc++20+libsigc++20-devel.info
+ error: multiple partially installed fedora packages for libsigc++
+ info: candidate: libsigc++30 3.0.7-2.fc35, missing components: libsigc++30-devel
+ info: candidate: libsigc++20 2.10.7-3.fc35, missing components: libsigc++20-devel
+ info: consider fully installing the desired package manually and retrying the bpkg command
+ EOE
+ }
+}
diff --git a/bpkg/system-package-manager.cxx b/bpkg/system-package-manager.cxx
index 5089db1..6a44b09 100644
--- a/bpkg/system-package-manager.cxx
+++ b/bpkg/system-package-manager.cxx
@@ -14,6 +14,7 @@
#include <bpkg/diagnostics.hxx>
#include <bpkg/system-package-manager-debian.hxx>
+#include <bpkg/system-package-manager-fedora.hxx>
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}