aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager-debian.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/system-package-manager-debian.hxx')
-rw-r--r--bpkg/system-package-manager-debian.hxx271
1 files changed, 271 insertions, 0 deletions
diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx
new file mode 100644
index 0000000..336f7a7
--- /dev/null
+++ b/bpkg/system-package-manager-debian.hxx
@@ -0,0 +1,271 @@
+// file : bpkg/system-package-manager-debian.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_HXX
+#define BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_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 Debian and alike (Ubuntu,
+ // etc) using the apt frontend (specifically, apt-get and apt-cache) for
+ // consumption and the dpkg-buildpackage/debhelper/dh tooling for
+ // production.
+ //
+ // NOTE: THE BELOW DESCRIPTION IS ALSO REPRODUCED IN THE BPKG MANUAL.
+ //
+ // For background, a library in Debian is normally split up into several
+ // packages: the shared library package (e.g., libfoo1 where 1 is the ABI
+ // version), the development files package (e.g., libfoo-dev), the
+ // documentation files package (e.g., libfoo-doc), the debug symbols package
+ // (e.g., libfoo1-dbg), and the (usually) architecture-independent files
+ // (e.g., libfoo1-common). All the packages except -dev are optional and
+ // there is quite a bit of variability here. Here are a few examples:
+ //
+ // libsqlite3-0 libsqlite3-dev
+ //
+ // libssl1.1 libssl-dev libssl-doc
+ // libssl3 libssl-dev libssl-doc
+ //
+ // libcurl4 libcurl4-openssl-dev libcurl4-doc
+ // libcurl3-gnutls libcurl4-gnutls-dev libcurl4-doc (yes, 3 and 4)
+ //
+ // Note that while most library package names in Debian start with lib (per
+ // the policy), there are exceptions (e.g., zlib1g zlib1g-dev). The
+ // header-only library package names may or may not start with lib and end
+ // with -dev (e.g., libeigen3-dev, rapidjson-dev, catch2).
+ //
+ // Also note that manual -dbg packages are obsolete in favor of automatic
+ // -dbgsym packages from Debian 9. So while we support -dbg for consumption,
+ // we only generate -dbgsym.
+ //
+ // Based on that, it seems our best bet when trying to automatically map our
+ // library package name to Debian package names is to go for the -dev
+ // package first and figure out the shared library package from that based
+ // on the fact that the -dev package should have the == dependency on the
+ // shared library package with the same version and its name should normally
+ // start with the -dev package's stem.
+ //
+ // For executable packages there is normally no -dev packages but -dbg,
+ // -doc, and -common are plausible.
+ //
+ // The format of the debian-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 be "package components" (for the
+ // lack of a better term) of the same "logical package", such as -dev, -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).
+ //
+ // We allow/recommend specifying the -dev 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 -dev (which poses an ambiguity), then
+ // the -dev 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 -dev, 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 Debian package version has the [<epoch>:]<upstream>[-<revision>] form
+ // (see deb-version(5) for details). If no explicit mapping to the bpkg
+ // version is specified with the debian-to-downstream-version (or alike)
+ // manifest values or none match, then we fallback to using the <upstream>
+ // part as the bpkg version. If explicit mapping is specified, then we match
+ // it against the [<epoch>:]<upstream> parts ignoring <revision>.
+ //
+ struct system_package_status_debian: system_package_status
+ {
+ string main;
+ string dev;
+ string doc;
+ string dbg;
+ string common;
+ strings extras;
+
+ // 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_policy> package_policies;
+ size_t package_policies_main = 0; // Size of the main group.
+
+ explicit
+ system_package_status_debian (string m, string d = {})
+ : main (move (m)), dev (move (d))
+ {
+ assert (!main.empty () || !dev.empty ());
+ }
+
+ system_package_status_debian () = default;
+ };
+
+ class system_package_manager_debian: public system_package_manager
+ {
+ public:
+ virtual optional<const system_package_status*>
+ status (const package_name&, const available_packages*) override;
+
+ virtual void
+ install (const vector<package_name>&) override;
+
+ virtual binary_files
+ generate (const packages&,
+ const packages&,
+ const strings&,
+ const dir_path&,
+ const package_manifest&,
+ const string&,
+ const small_vector<language, 1>&,
+ optional<bool>,
+ bool) override;
+
+ public:
+ // Expect os_release::name_id to be "debian" or os_release::like_ids to
+ // contain "debian".
+ //
+ // @@ TODO: we currently don't handle non-host arch in consumption.
+ //
+ system_package_manager_debian (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress,
+ optional<size_t> fetch_timeout,
+ bool install,
+ bool fetch,
+ bool yes,
+ string sudo)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress,
+ fetch_timeout,
+ install,
+ fetch,
+ yes,
+ move (sudo)) {}
+
+ // Note: options can only be NULL when testing functions that don't need
+ // them.
+ //
+ system_package_manager_debian (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress,
+ const pkg_bindist_options* ops)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress),
+ ops_ (ops) {}
+
+ // Implementation details exposed for testing (see definitions for
+ // documentation).
+ //
+ public:
+ using package_status = system_package_status_debian;
+ using package_policy = package_status::package_policy;
+
+ void
+ apt_cache_policy (vector<package_policy>&, size_t = 0);
+
+ string
+ apt_cache_show (const string&, const string&);
+
+ void
+ apt_get_update ();
+
+ void
+ apt_get_install (const strings&);
+
+ pair<cstrings, const process_path&>
+ apt_get_common (const char*, strings& args_storage);
+
+ static package_status
+ parse_name_value (const string&, const string&, bool, bool);
+
+ static string
+ main_from_dev (const string&, const string&, const string&);
+
+ static string
+ arch_from_target (const target_triplet&);
+
+ package_status
+ map_package (const package_name&,
+ const version&,
+ const available_packages&,
+ const optional<string>&) const;
+
+ // 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
+ {
+ std::map<strings, path> apt_cache_policy_;
+ std::map<strings, path> apt_cache_policy_fetched_;
+ std::map<strings, path> apt_cache_policy_installed_;
+
+ std::map<pair<string, string>, path> apt_cache_show_;
+ std::map<pair<string, string>, path> apt_cache_show_fetched_;
+
+ bool apt_get_update_fail_ = false;
+ bool apt_get_install_fail_ = false;
+ };
+
+ const simulation* simulate_ = nullptr;
+
+ private:
+ optional<system_package_status_debian>
+ status (const package_name&, const available_packages&);
+
+ private:
+ bool fetched_ = false; // True if already fetched metadata.
+ bool installed_ = false; // True if already installed.
+
+ std::map<package_name, optional<system_package_status_debian>> status_cache_;
+
+ const pkg_bindist_options* ops_ = nullptr; // Only for production.
+ };
+}
+
+#endif // BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_HXX