aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager.hxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-02-01 11:42:31 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-02-01 11:42:31 +0200
commit546391dab6173660acceba6404136e9411ce1388 (patch)
tree79da333fd1f7447c6b9490565f520d1d79a329b7 /bpkg/system-package-manager.hxx
parent724131b7e03934664621f86df2dc2285ff43dba8 (diff)
Implement system package manager query and install support for Debian
Diffstat (limited to 'bpkg/system-package-manager.hxx')
-rw-r--r--bpkg/system-package-manager.hxx270
1 files changed, 270 insertions, 0 deletions
diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx
new file mode 100644
index 0000000..9a9c443
--- /dev/null
+++ b/bpkg/system-package-manager.hxx
@@ -0,0 +1,270 @@
+// file : bpkg/system-package-manager.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_HXX
+#define BPKG_SYSTEM_PACKAGE_MANAGER_HXX
+
+#include <libbpkg/manifest.hxx> // version
+#include <libbpkg/package-name.hxx>
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/package.hxx>
+#include <bpkg/common-options.hxx>
+#include <bpkg/host-os-release.hxx>
+
+namespace bpkg
+{
+ // The system/distribution package manager interface. Used by both pkg-build
+ // (to query and install system packages) and by pkg-bindist (to build
+ // them).
+ //
+ // Note that currently the result of a query is a single available version.
+ // While some package managers may support having multiple available
+ // versions and may even allow installing multiple versions in parallel,
+ // supporting this on our side will complicate things quite a bit. While we
+ // can probably plug multiple available versions into our constraint
+ // satisfaction machinery, the rabbit hole goes deeper than that since, for
+ // example, different bpkg packages can be mapped to the same system
+ // package, as is the case for libcrypto/libssl which are both mapped to
+ // libssl on Debian. This means we will need to somehow coordinate (and
+ // likely backtrack) version selection between unrelated bpkg packages
+ // because only one underlying system version can be selected. (One
+ // simplified way to handle this would be to detect that different versions
+ // we selected and fail asking the user to resolve this manually.)
+ //
+ // Additionally, parallel installation 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).
+ //
+ // 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. Plus, even in the case of
+ // C/C++ libraries, there is still the plausible case of picking one of
+ // the multiple available version.
+ //
+ // On the other hand, the ultimate goal of system package managers, at least
+ // traditional ones like Debian and Fedora, is to end up with a single,
+ // usually the latest available, version of the package that is used by
+ // everyone. In fact, if one looks at a stable distributions of Debian and
+ // Fedora, they normally provide only a single version of each package. This
+ // decision will also likely simplify the implementation. For example, on
+ // Debian, it's straightforward to get the installed and candidate versions
+ // (e.g., from apt-cache policy). But getting all the possible versions that
+ // can be installed without having to specify the release explicitly is a
+ // lot less straightforward (see the apt-cache command documentation in The
+ // Debian Administrator's Handbook for background).
+ //
+ // So for now we keep it simple and pick a single available version but can
+ // probably revise this decision later.
+ //
+ struct system_package_status
+ {
+ // Downstream (as in, bpkg package) version.
+ //
+ bpkg::version version;
+
+ // System (as in, distribution) package name and version for diagnostics.
+ //
+ // Note that this status may represent multiple system packages (for
+ // example, libfoo and libfoo-dev) and here we have only the
+ // main/representative package name (for example, libfoo).
+ //
+ string system_name;
+ string system_version;
+
+ // The system package can be either "available already installed",
+ // "available partially installed" (for example, libfoo but not
+ // libfoo-dev is installed) or "available not yet installed".
+ //
+ enum status_type {installed, partially_installed, not_installed};
+
+ status_type status = not_installed;
+ };
+
+ class system_package_manager
+ {
+ public:
+ // Query the system package status.
+ //
+ // 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.
+ //
+ // The returned status can be NULL, which indicates that no such package
+ // is available from the system package manager. Note that NULL is also
+ // returned if no fully installed package is available from the system and
+ // package installation is not enabled (see the constructor below).
+ //
+ // Note also that the implementation is expected to issue appropriate
+ // progress and diagnostics if fetching package metadata (again see the
+ // constructor below).
+ //
+ virtual optional<const system_package_status*>
+ pkg_status (const package_name&, const available_packages*) = 0;
+
+ // Install the specified subset of the previously-queried packages.
+ // Should only be called if installation is enabled (see the constructor
+ // below).
+ //
+ // Note that this function should be called only once after the final set
+ // of the required system packages has been determined. And the specified
+ // subset should contain all the selected packages, including the already
+ // fully installed. This allows the implementation to merge and de-
+ // duplicate the system package set to be installed (since some bpkg
+ // packages may be mapped to the same system package), perform post-
+ // installation verifications (such as making sure the versions of already
+ // installed packages have not changed due to upgrades), change properties
+ // of already installed packages (e.g., mark them as manually installed in
+ // Debian), etc.
+ //
+ // Note also that the implementation is expected to issue appropriate
+ // progress and diagnostics.
+ //
+ virtual void
+ pkg_install (const vector<package_name>&) = 0;
+
+ public:
+ // If install is true, then enable package installation.
+ //
+ // 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.
+ //
+ system_package_manager (os_release&& osr,
+ const target_triplet& host,
+ bool install,
+ bool fetch,
+ optional<bool> progress,
+ bool yes,
+ string sudo)
+ : os_release_ (osr),
+ host_ (host),
+ progress_ (progress),
+ install_ (install),
+ fetch_ (fetch),
+ yes_ (yes),
+ sudo_ (sudo != "false" ? move (sudo) : string ()) {}
+
+ virtual
+ ~system_package_manager ();
+
+ // Implementation details.
+ //
+ public:
+ // Given the available packages (as returned by find_available_all())
+ // return the list of system package names as mapped by the
+ // <distribution>-name values.
+ //
+ // 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 <distribution>-name values corresponding to name_id.
+ // Assume <distribution> has the <name>[_<version>] form, where <version>
+ // is a semver-like version (e.g, 10, 10.15, or 10.15.1) and return all
+ // the values that are equal or less than the specified version_id
+ // (include the value with the absent <version>). In a sense, absent
+ // <version> can be treated as a 0 semver-like version.
+ //
+ // 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
+ // something more elaborate, like translate version_id to one of the
+ // like_id's version and try that).
+ //
+ // Note that multiple -name values per same distribution can be returned
+ // as, for example, for the following distribution values:
+ //
+ // debian_10-name: libcurl4 libcurl4-doc libcurl4-openssl-dev
+ // debian_10-name: libcurl3-gnutls libcurl4-gnutls-dev (yes, 3 and 4)
+ //
+ // Note also that the values are returned in the "override order", that is
+ // from the newest package version to oldest and then from the highest
+ // distribution version to lowest.
+ //
+ static strings
+ system_package_names (const available_packages&,
+ const string& name_id,
+ const string& version_id,
+ const vector<string>& like_ids);
+
+ // Given the system package version and available packages (as returned by
+ // find_available_all()) return the downstream package version as mapped
+ // by one of the <distribution>-to-downstream-version values.
+ //
+ // The rest of the arguments as well as the overalls semantics is the same
+ // as in system_package_names() above. That is, first consider
+ // <distribution>-to-downstream-version values corresponding to
+ // name_id. If none match, then repeat the above process for every
+ // like_ids entry with version_id equal 0. If still no match, then return
+ // nullopt (in which case the caller may choose to fallback to the system
+ // package version or do something more elaborate).
+ //
+ static optional<version>
+ downstream_package_version (const string& system_version,
+ const available_packages&,
+ const string& name_id,
+ const string& version_id,
+ const vector<string>& like_ids);
+ protected:
+ os_release os_release_;
+ target_triplet host_;
+ optional<bool> progress_; // --[no]-progress (see also stderr_term)
+
+ // The --sys-* option values.
+ //
+ bool install_;
+ bool fetch_;
+ bool yes_;
+ string sudo_;
+ };
+
+ // Create a package manager instance corresponding to the specified host
+ // target and optional manager name. If name is empty, return NULL if there
+ // is no support for this platform. Currently recognized names:
+ //
+ // debian -- Debian and alike (Ubuntu, etc) using the APT frontend.
+ // fedora -- Fedora and alike (RHEL, Centos, etc) using the DNF frontend.
+ //
+ // Note: the name can be used to select an alternative package manager
+ // implementation on platforms that support multiple.
+ //
+ unique_ptr<system_package_manager>
+ make_system_package_manager (const common_options&,
+ const target_triplet&,
+ bool install,
+ bool fetch,
+ bool yes,
+ const string& sudo,
+ const string& name);
+}
+
+#endif // BPKG_SYSTEM_PACKAGE_MANAGER_HXX