From ee6b05b76ec3f8439ed9475e7f987dfb22395e82 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 26 Jan 2023 20:58:09 +0300 Subject: Start Fedora implementation --- bpkg/system-package-manager-fedora.cxx | 754 +++++++++++++++++++++++++++++++++ bpkg/system-package-manager-fedora.hxx | 234 ++++++++++ bpkg/system-package-manager.cxx | 18 + 3 files changed, 1006 insertions(+) create mode 100644 bpkg/system-package-manager-fedora.cxx create mode 100644 bpkg/system-package-manager-fedora.hxx diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx new file mode 100644 index 0000000..a22bfba --- /dev/null +++ b/bpkg/system-package-manager-fedora.cxx @@ -0,0 +1,754 @@ +// file : bpkg/system-package-manager-fedora.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace butl; + +namespace bpkg +{ + using package_status = system_package_status_fedora; + + // Parse the fedora-name (or alike) value. + // + // Note that for now we treat all the packages from the non-main groups as + // extras omitting the -common package (assuming it's pulled by the main + // package) as well as -doc and -dbg unless requested with the + // extra_{doc,dbg} arguments. + // + package_status system_package_manager_fedora:: + parse_name_value (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) + { + strings ns (split (g, ' ')); + + if (ns.empty ()) + fail << "empty package group"; + + package_status r; + + // Handle the devel instead of main special case for libraries. + // + // 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., libops-devel libops-devel-devel). + // + { + string& m (ns[0]); + + if (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 = "-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]); + else + { + package_status g (parse_group (gs[i])); + + if (!g.main.empty ()) r.extras.push_back (move (g.main)); + if (!g.devel.empty ()) r.extras.push_back (move (g.devel)); + 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; + } + + 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& pps, size_t n) + { + if (n == 0) + n = pps.size (); + + assert (n != 0 && n <= pps.size ()); + + // In particular, --quiet makes sure we don't get 'Last metadata + // expiration check: ' printed to stderr. It does not appear to + // affect error diagnostics (try specifying an unknown package). + // + cstrings args { + "dnf", "list", + "--all", + "--cacheonly" + "--quiet"}; + + for (size_t i (0); i != n; ++i) + { + package_policy& pp (pps[i]); + + string& n (pp.name); + assert (!n.empty ()); + + pp.installed_version.clear (); + pp.candidate_version.clear (); + + n += '.'; + n += host_.cpu; + + args.push_back (n.c_str ()); + } + + args.push_back ("dnf.noarch"); + + 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 + { +#if 0 + strings k; + for (size_t i (0); i != n; ++i) + k.push_back (pps[i].name); + + const path* f (nullptr); + if (installed_) + { + auto i (simulate_->apt_cache_policy_installed_.find (k)); + if (i != simulate_->apt_cache_policy_installed_.end ()) + f = &i->second; + } + if (f == nullptr && fetched_) + { + auto i (simulate_->apt_cache_policy_fetched_.find (k)); + if (i != simulate_->apt_cache_policy_fetched_.end ()) + f = &i->second; + } + if (f == nullptr) + { + auto i (simulate_->apt_cache_policy_.find (k)); + if (i != simulate_->apt_cache_policy_.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)); +#endif + } + + try + { + ifdstream is (move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit); + + // The output of `dnf list . . ...` is the 2 + // group of lines in the following form: + // + // Installed Packages + // . 13.0.0-3.fc35 @ + // . 69.1-6.fc35 @ + // Available Packages + // . 13.0.1-1.fc35 + // . 1.2.11-32.fc35 + // + // Where unknown packages are omitted. The lines order is not + // necessarily matches 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. + // + { + auto df = make_diag_frame ( + [&pe, &args] (diag_record& dr) + { + dr << info << "while parsing output of "; + print_process (dr, pe, args); + }); + + optional installed; + for (string l; !eof (getline (is, l)); ) + { + if (l == "Installed Packages") + { + if (installed) + fail << "unexpected line '" << l << "': must be first"; + + installed = true; + continue; + } + + if (l == "Available Packages") + { + if (installed && !*installed) + fail << "duplicate line '" << l << "'"; + + installed = false; + continue; + } + + if (!installed) + fail << "unexpected line '" << l << "'"; + + 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); + + size_t b (l.find_first_not_of (' ', e)); + + if (b == string::npos) + fail << "expected package version in '" << l << "'"; + + e = l.find (' ', b); + + 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 + // for good measure verify that it also contains a repository id. + // + b = l.find_first_not_of (' ', e); + + if (b == string::npos) + fail << "expected package repository in '" << l << "'"; + + if (p == "dnf.noarch") + continue; + + // Find the package. + // + auto i (find_if (pps.begin (), pps.end (), + [&p] (const package_policy& pp) + {return pp.name == p;})); + + if (i == pps.end ()) + fail << "unexpected package name '" << p << "' in '" << l << "'"; + + (*installed ? i->installed_version : i->candidate_version) = + move (v); + } + } + + 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 (); + } + } + + // Execute `dnf repoquery --requires` and return the dependency packages as + // a list of the name/version pairs. Fail if either package or version is + // unknown. + // + // Note that if the package or version is unknown then the empty list is + // returned. + // + vector> system_package_manager_fedora:: + dnf_repoquery_requires (const string& name, const string& ver) + { + assert (!name.empty () && !ver.empty ()); + + string spec (name + '-' + ver); + + // In particular, --quiet makes sure we don't get 'Last metadata + // expiration check: ' printed to stderr. It does not appear to + // affect error diagnostics (try specifying an unknown option). + // + const char* args[] = { + "dnf", "repoquery", "--requires", + "--resolve", + "--arch", host_.cpu.c_str (), + "--qf", "%{name} %{version}-%{release}", + "--cacheonly", + "--quiet", + spec.c_str (), + nullptr}; + + // Note that for this command there seems to be no need to run with the C + // locale since the output is presumably not localizable. But let's do it + // for good measure. + // + const char* evars[] = {"LC_ALL=C", nullptr}; + + vector> r; + try + { + if (dnf_path.empty () && !simulate_) + dnf_path = process::path_search (args[0]); + + process_env pe (dnf_path, evars); + + if (verb >= 3) + print_process (pe, args); + + // Redirect stdout to a pipe. For good measure also redirect stdin to + // /dev/null to make sure there are no prompts of any kind. + // + process pr; + if (!simulate_) + pr = process (dnf_path, + args, + -2 /* stdin */, + -1 /* stdout */, + 2 /* stderr */, + nullptr /* cwd */, + evars); + else + { + // @@ TODO + // +#if 0 + pair k (name, ver); + + const path* f (nullptr); + if (fetched_) + { + auto i (simulate_->apt_cache_show_fetched_.find (k)); + if (i != simulate_->apt_cache_show_fetched_.end ()) + f = &i->second; + } + if (f == nullptr) + { + auto i (simulate_->apt_cache_show_.find (k)); + if (i != simulate_->apt_cache_show_.end ()) + f = &i->second; + } + + diag_record dr (text); + print_process (dr, pe, args); + dr << " <" << (f == nullptr || f->empty () ? "/dev/null" : f->string ()); + + if (f == nullptr || f->empty ()) + { + text << "E: No packages found"; + pr = process (process_exit (100)); + } + else + { + pr = process (process_exit (0)); + pr.in_ofd = f->string () == "-" + ? fddup (stdin_fd ()) + : fdopen (*f, fdopen_mode::in); + } +#endif + } + + try + { + ifdstream is (move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit); + + // The output of `dnf repoquery --requires -` will be the + // sequence of the dependency package lines in the ` ` + // form. Here is a representative example: + // + // bash 5.1.8-3.fc35 + // glibc 2.34-49.fc35 + // libicu 69.1-6.fc35 + // libicu-devel 69.1-6.fc35 + // pkgconf-pkg-config 1.8.0-1.fc35 + // + for (string l; !eof (getline (is, l)); ) + { + size_t p (l.find (' ')); + + if (p == string::npos) + fail << "expected package name and version instead of '" << l + << "'"; + + // Split the line into the package name and version. + // + string v (l, p + 1); + l.resize (p); // Name. + + // Skip the potential self-dependency line (see the above example). + // + if (l == name && v == ver) + continue; + + r.emplace_back (move (l), move (v)); + } + + is.close (); + } + catch (const io_error& e) + { + if (pr.wait ()) + fail << "unable to read " << args[0] << " --requires output: " << e; + + // Fall through. + } + + if (!pr.wait ()) + { + diag_record dr (fail); + dr << args[0] << " --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; + } + + optional system_package_manager_fedora:: + pkg_status (const package_name& pn, const available_packages* aps) + { + // For now we ignore -doc and -debug* package components (but we may want + // to have options controlling this later). Note also that we assume + // -common is pulled automatically by the base package so we ignore it as + // well. + // + bool need_doc (false); + bool need_debuginfo (false); + bool need_debugsource (false); + + // First check the cache. + // + { + auto i (status_cache_.find (pn)); + + if (i != status_cache_.end ()) + return i->second ? &*i->second : nullptr; + + if (aps == nullptr) + return nullopt; + } + + vector candidates; + + // Translate our package name to the Fedora package names. + // + { + auto df = make_diag_frame ( + [this, &pn] (diag_record& dr) + { + dr << info << "while mapping " << pn << " to " + << os_release_.name_id << " package name"; + }); + + strings ns (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 (see above for + // details). + // + const string& n (pn.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. + // + // @@ We should probably to also/instead consider the project name. We + // will need to add it to available_package type then and take it + // from the latest available package. + // + // const string* prj (aps != nullptr && aps->front ().project + // ? &aps->front ().project->string () + // : nullptr); + // + if (n.compare (0, 3, "lib") == 0) + { + // Keep the base package name empty as an indication that it is to + // be discovered. + // + candidates.push_back (package_status ("", n + "-devel")); + + // @@ Add the project-based candidate. + // + // if (prj != nullptr) + // candidates.push_back (package_status ("", *prj + "-devel")); + } + else + { + candidates.push_back (package_status (n)); + + // @@ Add the project-based candidate. + // + // if (prj != nullptr) + // candidates.push_back (package_status ("", *prj)); + } + } + else + { + // Parse each manual mapping. + // + for (const string& n: ns) + { + package_status s ( + parse_name_value (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) + { + return s.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? + } + } + } + } + + // Guess unknown main package given the devel package and its version. + // + auto guess_main = [this, &pn] (package_status& s, const string& ver) + { + vector> depends ( + dnf_repoquery_requires (s.devel, ver)); +#if 0 + s.main = main_from_dev (s.dev, ver, depends); + + if (s.main.empty ()) + { + fail << "unable to guess main Debian package for " << s.dev << ' ' + << ver << + info << s.dev << " Depends value: " << depends << + info << "consider specifying explicit mapping in " << pn + << " package manifest"; + } +#endif + }; + + // First look for an already fully installed package. + // + optional r; + + for (package_status& ps: candidates) + { + vector& pps (ps.package_policies); + + if (!ps.main.empty ()) pps.emplace_back (ps.main); + if (!ps.devel.empty ()) pps.emplace_back (ps.devel); + if (!ps.doc.empty () && need_doc) pps.emplace_back (ps.doc); + + if (!ps.debuginfo.empty () && need_debuginfo) + pps.emplace_back (ps.debuginfo); + + if (!ps.debugsource.empty () && need_debugsource) + pps.emplace_back (ps.debugsource); + + if (!ps.common.empty () && false) pps.emplace_back (ps.common); + ps.package_policies_main = pps.size (); + for (const string& n: ps.extras) pps.emplace_back (n); + + dnf_list (pps); + + // Handle the unknown main package. + // + if (ps.main.empty ()) + { + const package_policy& devel (pps.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); + pps.emplace (pps.begin (), ps.main); + ps.package_policies_main++; + dnf_list (pps, 1); + } + +#if 0 + optional s (status (pps, ps.package_policies_main)); + + if (!s) + continue; + + if (*s == package_status::installed) + { + const package_policy& main (pps.front ()); + + ps.status = *s; + ps.system_name = main.name; + ps.system_version = main.installed_version; + + if (r) + { + fail << "multiple installed " << os_release_.name_id + << " packages for " << pn << + info << "first package: " << r->main << " " << r->system_version << + info << "second package: " << ps.main << " " << ps.system_version << + info << "consider specifying the desired version manually"; + } + + r = move (ps); + } +#endif + } + + // Cache. + // + auto i (status_cache_.emplace (pn, move (r)).first); + return i->second ? &*i->second : nullptr; + } + + void system_package_manager_fedora:: + pkg_install (const vector& /*pns*/) + { + // @@ TODO + } +} diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx new file mode 100644 index 0000000..129a9d2 --- /dev/null +++ b/bpkg/system-package-manager-fedora.hxx @@ -0,0 +1,234 @@ +// file : bpkg/system-package-manager-fedora.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX +#define BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX + +#include +#include + +#include + +namespace bpkg +{ + // The system package manager implementation for Fedora and alike (Red Hat + // Enterprise Linux, CentOS, etc) using the DNF frontend. + // + // 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 have the -libs suffix (e.g., foo-libs). + // + // 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-devel openssl-libs + // + // 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 + // + // 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 value is a comma- + // separated list of one or more package groups: + // + // [, ...] + // + // Where each is the space-separated list of one or more + // package names: + // + // [ ...] + // + // All the packages in the group should belong to the same "logical + // package", such as -devel, -doc, -common packages. They normally have the + // same version. + // + // The first group is called the main group and the first package in the + // group is called the main package. Note that all the groups are consumed + // (installed) but only the main group is produced (packaged). + // + // (Note that above we use the term "logical package" instead of "base + // package" since the main package may not be the base package, for example + // being the -libs subpackage.) + // + // We allow/recommend specifying the -devel package instead of the main + // package for libraries (the bpkg package name starts with lib), seeing + // that we are capable of detecting the main package automatically. If the + // library name happens to end with -devel (which poses an ambiguity), then + // the -devel package should be specified explicitly as the second package + // to disambiguate this situation (if a non-library name happened to start + // with lib and end with -devel, well, you are out of luck, I guess). + // + // Note also that for now we treat all the packages from the non-main groups + // as extras but in the future we may decide to sort them out like the main + // group (see parse_name_value() for details). + // + // The Fedora package version has the [:]- form + // where the parts correspond to the Epoch (optional upstream versioning + // scheme), Version (upstream version), and Release (Fedora's package + // revision) RPM tags (see the Fedora package Versioning Guidelines and RPM + // tags documentation for details). If no explicit mapping to bpkg version + // is specified with the fedora-to-downstream-version manifest values (or + // alike), then we fallback to using the part as bpkg version. If + // explicit mapping is specified, then we match it against the + // [:] parts ignoring . + // + struct system_package_status_fedora: system_package_status + { + string main; + string devel; + string doc; + string debuginfo; + string debugsource; + string common; + strings extras; + + string devel_fallback; // Fallback based on project name. + + // @@ Rename. package_info? + // + // The `apt-cache policy` output. + // + struct package_policy + { + string name; + string installed_version; // Empty if none. + string candidate_version; // Empty if none and no installed_version. + + explicit + package_policy (string n): name (move (n)) {} + }; + + vector package_policies; + size_t package_policies_main = 0; // Size of the main group. + + explicit + system_package_status_fedora (string m, string d = {}) + : main (move (m)), devel (move (d)) + { + assert (!main.empty () || !devel.empty ()); + } + + system_package_status_fedora () = default; + }; + + class system_package_manager_fedora: public system_package_manager + { + public: + virtual optional + pkg_status (const package_name&, const available_packages*) override; + + virtual void + pkg_install (const vector&) override; + + public: + // Note: expects os_release::name_id to be "fedora" or os_release::like_id + // 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_policy = package_status::package_policy; + + void + dnf_list (vector&, size_t = 0); + + vector> + dnf_repoquery_requires (const string&, const string&); + + static package_status + parse_name_value (const string&, bool, bool, bool); + + // @@ TODO + // + // If simulate is not NULL, then instead of executing the actual apt-cache + // and apt-get commands simulate their execution: (1) for apt-cache by + // printing their command lines and reading the results from files + // specified in the below apt_cache_* maps and (2) for apt-get 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 apt-cache + // different post-fetch and (for policy) 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 + // apt_cache_policy_* maps are the package sets and the corresponding + // result file is expected to contain (or not) the results for all of + // them. See apt_cache_policy() and apt_cache_show() implementations for + // details on the expected results. + // + struct simulation + { +#if 0 + std::map apt_cache_policy_; + std::map apt_cache_policy_fetched_; + std::map apt_cache_policy_installed_; + + std::map, path> apt_cache_show_; + std::map, path> apt_cache_show_fetched_; + + bool apt_get_update_fail_ = false; + bool apt_get_install_fail_ = false; +#endif + }; + + const simulation* simulate_ = nullptr; + + protected: + bool fetched_ = false; // True if already fetched metadata. + bool installed_ = false; // True if already installed. + + std::map> status_cache_; + }; +} + +#endif // BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX diff --git a/bpkg/system-package-manager.cxx b/bpkg/system-package-manager.cxx index 9f5dad0..2e48fe0 100644 --- a/bpkg/system-package-manager.cxx +++ b/bpkg/system-package-manager.cxx @@ -14,6 +14,7 @@ #include #include +#include using namespace std; using namespace butl; @@ -71,6 +72,23 @@ 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 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"); + + // @@ TODO: verify name if specified. + + r.reset (new system_package_manager_fedora ( + move (*osr), host, install, fetch, progress, yes, sudo)); + } } } -- cgit v1.1