From d1f58962fa9953a9ed0d2c72be5d86f3d6605804 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 17 Jan 2023 12:53:18 +0200 Subject: Switch to multi-version interface, start Debian implementation --- bpkg/system-package-manager-debian.cxx | 88 +++++++++++++++++++++++++++ bpkg/system-package-manager-debian.hxx | 9 ++- bpkg/system-package-manager.cxx | 51 +++++++++++----- bpkg/system-package-manager.hxx | 107 ++++++++++++++++++--------------- 4 files changed, 189 insertions(+), 66 deletions(-) diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx index 1e43206..21b7e04 100644 --- a/bpkg/system-package-manager-debian.cxx +++ b/bpkg/system-package-manager-debian.cxx @@ -25,4 +25,92 @@ namespace bpkg // (e.g., openssl-devel) so probably should track the status of // individual system packages. What if we "installed any version" // first and then need to install specific? + + auto system_package_manager_debian:: + pkg_status (const package_name& pn, + const available_packages* aps, + bool install, + bool fetch) -> const vector* + { + // First check the cache. + // + { + auto i (status_cache_.find (pn)); + + if (i != status_cache_.end ()) + return &i->second; + + if (aps == nullptr) + return nullptr; + } + + // Translate our package name to the system package names. + // + strings spns (system_package_names (*aps, + os_release_.name_id, + os_release_.version_id, + os_release_.like_ids)); + + // @@ TODO: fallback to our package name if empty (plus -dev if lib). + // @@ TODO: split into packages/components + + vector r; + + // First look for an already installed package. + // + + + // Next look for available versions if we are allowed to install. + // + // We only do this if we don't have a package already installed. This is + // because while Debian generally supports installing multiple versions in + // parallel, this is unlikely to be supported for the packages we are + // interested in due to the underlying limitations. + // + // Specifically, the packages that we are primarily interested in are + // libraries with headers and executables (tools). While Debian is able to + // install multiple libraries in parallel, it normally can only install a + // single set of headers, static libraries, pkg-config files, etc., (i.e., + // the -dev package) at a time due to them being installed into the same + // location (e.g., /usr/include). The same holds for executables, which + // are installed into the same location (e.g., /usr/bin). + // + // It is possible that a certain library has made arrangements for + // multiple of its versions to co-exist. For example, hypothetically, our + // libssl package could be mapped to both libssl1.1 libssl1.1-dev and + // libssl3 libssl3-dev which could be installed at the same time (note + // that it is not the case in reality; there is only libssl-dev). However, + // in this case, we should probably also have two packages with separate + // names (e.g., libssl and libssl3) that can also co-exist. An example of + // this would be libQt5Core and libQt6Core. (Note that strictly speaking + // there could be different degrees of co-existence: for the system + // package manager it is sufficient for different versions not to clobber + // each other's files while for us we may also need the ability to use + // different versions in the base build). + // + // Note also that the above reasoning is quite C/C++-centric and it's + // possible that multiple versions of libraries (or equivalent) for other + // languages (e.g., Rust) can always co-exist. In this case we may need to + // revise this decision. + // + if (install && r.empty ()) + { + if (fetch && !fetched_) + { + // @@ TODO: apt-get update + + fetched_ = true; + } + } + + // Cache. + // + return &status_cache_.emplace (pn, move (r)).first->second; + } + + bool system_package_manager_debian:: + pkg_install (const package_name&, const version&) + { + return false; + } } diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx index 5b1f764..ed74db0 100644 --- a/bpkg/system-package-manager-debian.hxx +++ b/bpkg/system-package-manager-debian.hxx @@ -17,18 +17,25 @@ namespace bpkg class system_package_manager_debian: public system_package_manager { public: - virtual optional + virtual const vector* pkg_status (const package_name&, const available_packages*, bool install, bool fetch) override; + virtual bool + pkg_install (const package_name&, const version&) override; + public: + // Note: expects os_release::name_id to be "debian" or os_release::like_id + // to contain "debian". + // explicit system_package_manager_debian (os_release&& osr) : system_package_manager (move (osr)) {} protected: + bool fetched_ = false; // True if already fetched metadata. }; } diff --git a/bpkg/system-package-manager.cxx b/bpkg/system-package-manager.cxx index 215ea47..d535140 100644 --- a/bpkg/system-package-manager.cxx +++ b/bpkg/system-package-manager.cxx @@ -31,16 +31,27 @@ namespace bpkg if (optional osr = host_os_release (host)) { + auto is_or_like = [&osr] (const char* id) + { + return (osr->name_id == id || + find_if (osr->like_ids.begin (), osr->like_ids.end (), + [id] (const string& n) + { + return n == id; + }) != osr->like_ids.end ()); + }; + if (host.class_ == "linux") { - if (osr->name_id == "debian" || - osr->name_id == "ubuntu" || - find_if (osr->like_ids.begin (), osr->like_ids.end (), - [] (const string& n) - { - return n == "debian" || n == "ubuntu"; - }) != osr->like_ids.end ()) + if (is_or_like ("debian") || + is_or_like ("ubuntu")) { + // If we recognized this as Debian-like in an ad hoc manner, then + // add debian to like_ids. + // + if (osr->name_id != "debian" && !is_or_like ("debian")) + osr->like_ids.push_back ("debian"); + // @@ TODO: verify name if specified. // @@ TMP @@ -64,7 +75,7 @@ namespace bpkg system_package_names (const available_packages& aps, const string& name_id, const string& version_id, - const string& like_id) + const vector& like_ids) { assert (!aps.empty ()); @@ -169,20 +180,30 @@ namespace bpkg // values vs (name_values (name_id, vid)); - // If the resulting list is empty and the like id is specified, then + // If the resulting list is empty and the like ids are specified, then // re-collect but now using the like id and "0" version id instead. // - if (vs.empty () && !like_id.empty ()) - vs = name_values (like_id, semantic_version (0, 0, 0)); + if (vs.empty ()) + { + for (const string& like_id: like_ids) + { + vs = name_values (like_id, semantic_version (0, 0, 0)); + if (!vs.empty ()) + break; + } + } // Return the values of the collected name/values list. // strings r; - r.reserve (vs.size ()); + if (size_t n = vs.size ()) + { + r.reserve (n); - transform (vs.begin (), vs.end (), - back_inserter (r), - [] (const distribution_name_value& v) {return v.value;}); + transform (vs.begin (), vs.end (), + back_inserter (r), + [] (const distribution_name_value& v) {return v.value;}); + } return r; } diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx index fb90e2c..78b157a 100644 --- a/bpkg/system-package-manager.hxx +++ b/bpkg/system-package-manager.hxx @@ -4,6 +4,8 @@ #ifndef BPKG_SYSTEM_PACKAGE_MANAGER_HXX #define BPKG_SYSTEM_PACKAGE_MANAGER_HXX +#include + #include // version #include @@ -31,6 +33,10 @@ namespace bpkg // "available partially installed" (for example, libfoo but not // libfoo-dev is installed) or "available not yet installed". // + // Whether not_installed versions can be returned along with installed + // or partially_installed depends on whether the packager manager can + // install multiple versions side-by-side. + // enum {installed, partially_installed, not_installed} status; // System (as in, distribution package) name and version. @@ -40,6 +46,32 @@ namespace bpkg string system_name; string system_version; */ + + // Package manager implementation-specific data. + // + public: + using data_ptr = unique_ptr; + + template + T& + data () { return *static_cast (data_.get ()); } + + template + const T& + data () const { return *static_cast (data_.get ()); } + + template + T& + data (T* d) + { + data_ = data_ptr (d, [] (void* p) { delete static_cast (p); }); + return *d; + } + + static void + null_data_deleter (void* p) { assert (p == nullptr); } + + data_ptr data_ = {nullptr, null_data_deleter}; }; // Query the system package status. @@ -47,62 +79,36 @@ namespace bpkg // This function has two modes: cache-only (available_packages is NULL) // and full (available_packages is not NULL). In the cache-only mode this // function returns the status of this package if it has already been - // queried and nullopt otherwise. This allows the caller to only collect - // all the available packages (for the name/version mapping information) - // if really necessary. + // queried and NULL otherwise. This allows the caller to only collect all + // the available packages (for the name/version mapping information) if + // really necessary. // - // The returned value can be NULL, which indicates that no such package is - // available from the system package manager. Note that NULL is returned - // if no fully installed package is available from the system and install - // is false. + // The returned value can be empty, which indicates that no such package + // is available from the system package manager. Note that empty is also + // returned if no fully installed package is available from the system and + // the install argument is false. // // If fetch is false, then do not re-fetch the system package repository // metadata (that is, available packages/versions) before querying for // the available version of the not yet installed or partially installed // packages. // - // Note that currently the result is a single available version. While - // some package managers may support installing multiple versions in - // parallel, this is unlikely to be suppored for the packages we are - // interested in due to the underlying limitations. - // - // Specifically, the packages that we are primarily interested in are - // libraries with headers and executables (tools). While most package - // managers (e.g., Debian, Fedora) are able to install multiple libraries - // in parallel, they normally can only install a single set of headers, - // static libraries, pkg-config files, etc., (e.g., -dev/-devel package) - // at a time due to them being installed into the same location (e.g., - // /usr/include). The same holds for executables, which are installed into - // the same location (e.g., /usr/bin). - // - // @@ But it's still plausible to have multiple available versions but - // only being able to install one at a time? - // - // It is possible that a certain library has made arrangements for - // multiple of its versions to co-exist. For example, hypothetically, our - // libssl package could be mapped to both libssl1.1 libssl1.1-dev and - // libssl3 libssl3-dev which could be installed at the same time (note - // that it is not the case in reality; there is only libssl-dev). However, - // in this case, we should probably also have two packages with separate - // names (e.g., libssl and libssl3) that can also co-exist. An example of - // this would be libQt5Core and libQt6Core. (Note that strictly speaking - // there could be different degrees of co-existence: for the system - // package manager it is sufficient for different versions not to clobber - // each other's files while for us we may also need the ability to use - // different versions in the base build). - // - // Note also that the above reasoning is quite C/C++-centric and it's - // possible that multiple versions of libraries (or equivalent) for other - // languages (e.g., Rust) can always co-exist. In this case we may need to - // revise this decision to only return a single version (and pick the best - // suitable version as part of the constraint resolution). - // - virtual optional + virtual const vector* pkg_status (const package_name&, const available_packages*, bool install, bool fetch) = 0; + // Install the previously-queried package that is not installed or + // partially installed. + // + // Return false if the installation was aborted by the user (for example, + // the user answered 'N' to the prompt). @@ Do we really need this? We + // may not always be able to distinguish. + // + virtual bool + pkg_install (const package_name&, const version&) = 0; + public: virtual ~system_package_manager (); @@ -115,9 +121,9 @@ namespace bpkg // Given the available packages (as returned by find_available_all()) // return the list of system package names. // - // The name_id, version_id, and like_id are the values from the os_release - // struct (refer there for background). If version_id is empty, then it's - // treated as "0". + // The name_id, version_id, and like_ids are the values from os_release + // (refer there for background). If version_id is empty, then it's treated + // as "0". // // First consider -name values corresponding to name_id. // Assume has the [_] form, where @@ -126,8 +132,8 @@ namespace bpkg // (include the value with the absent ). In a sense, absent // can be treated as 0 semver-like versions. // - // If no value is found and like_id is not empty, then repeat the above - // process for like_id instead of name_id and version_id equal 0. + // If no value is found then repeat the above process for every like_ids + // entry (from left to right) instead of name_id with version_id equal 0. // // If still no value is found, then return empty list (in which case the // caller may choose to fallback to the downstream package name or do @@ -138,10 +144,11 @@ namespace bpkg system_package_names (const available_packages&, const string& name_id, const string& version_id, - const string& like_id); + const vector& like_ids); protected: os_release os_release_; + std::map> status_cache_; }; // Create a package manager instance corresponding to the specified host -- cgit v1.1