aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-01-16 13:53:43 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2023-01-16 16:22:15 +0300
commit8e20c1ed163be60cb576b5ad8f5d27b6320154a6 (patch)
treebde57b0b18400d37f87df53d6889838ab592e4cd
parentc7e76c23b49c423f352b283c7afba248b4ce77e9 (diff)
Add os_release facility
-rw-r--r--bpkg/host-os-release.cxx121
-rw-r--r--bpkg/host-os-release.hxx57
-rw-r--r--bpkg/host-os-release.test.cxx55
-rw-r--r--bpkg/host-os-release.test.testscript178
-rw-r--r--bpkg/package-query.hxx2
-rw-r--r--bpkg/pkg-build.cxx3
-rw-r--r--bpkg/system-package-manager.cxx25
-rw-r--r--bpkg/system-package-manager.hxx73
8 files changed, 507 insertions, 7 deletions
diff --git a/bpkg/host-os-release.cxx b/bpkg/host-os-release.cxx
new file mode 100644
index 0000000..1a5a980
--- /dev/null
+++ b/bpkg/host-os-release.cxx
@@ -0,0 +1,121 @@
+// file : bpkg/host-os-release.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <bpkg/host-os-release.hxx>
+
+#include <libbutl/string-parser.hxx> // parse_quoted()
+
+#include <bpkg/diagnostics.hxx>
+
+using namespace butl;
+
+namespace bpkg
+{
+ // Note: not static for access from the unit test.
+ //
+ os_release
+ host_os_release_linux (path f = {})
+ {
+ os_release r;
+
+ // According to os-release(5), we should use /etc/os-release and fallback
+ // to /usr/lib/os-release if the former does not exist. It also lists the
+ // fallback values for individual variables, in case some are not present.
+ //
+ if (!f.empty ()
+ ? exists (f)
+ : (exists (f = path ("/etc/os-release")) ||
+ exists (f = path ("/usr/lib/os-release"))))
+ {
+ try
+ {
+ ifdstream ifs (f, ifdstream::badbit);
+
+ string l;
+ for (uint64_t ln (1); !eof (getline (ifs, l)); ++ln)
+ {
+ trim (l);
+
+ // Skip blanks lines and comments.
+ //
+ if (l.empty () || l[0] == '#')
+ continue;
+
+ // The variable assignments are in the "shell style" and so can be
+ // quoted/escaped. For now we only handle quoting, which is what all
+ // the instances seen in the wild seems to use.
+ //
+ size_t p (l.find ('='));
+ if (p == string::npos)
+ continue;
+
+ string n (l, 0, p);
+ l.erase (0, p + 1);
+
+ using string_parser::parse_quoted;
+ using string_parser::invalid_string;
+
+ try
+ {
+ if (n == "ID_LIKE")
+ {
+ r.like_ids.clear ();
+
+ vector<string> vs (parse_quoted (l, true /* unquote */));
+ for (const string& v: vs)
+ {
+ for (size_t b (0), e (0); next_word (v, b, e); )
+ {
+ r.like_ids.push_back (string (v, b, e - b));
+ }
+ }
+ }
+ else if (string* p = (n == "ID" ? &r.name_id :
+ n == "VERSION_ID" ? &r.version_id :
+ n == "VARIANT_ID" ? &r.variant_id :
+ n == "NAME" ? &r.name :
+ n == "VERSION_CODENAME" ? &r.version_codename :
+ n == "VARIANT" ? &r.variant :
+ nullptr))
+ {
+ vector<string> vs (parse_quoted (l, true /* unquote */));
+ switch (vs.size ())
+ {
+ case 0: *p = ""; break;
+ case 1: *p = move (vs.front ()); break;
+ default: throw invalid_string (0, "multiple values");
+ }
+ }
+ }
+ catch (const invalid_string& e)
+ {
+ location loc (move (f).string (), ln);
+ fail (loc) << "invalid " << n << " value: " << e;
+ }
+ }
+
+ ifs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read from " << f << ": " << e;
+ }
+ }
+
+ // Assign fallback values.
+ //
+ if (r.name_id.empty ()) r.name_id = "linux";
+ if (r.name.empty ()) r.name = "Linux";
+
+ return r;
+ }
+
+ optional<os_release>
+ host_os_release (const target_triplet& host)
+ {
+ if (host.class_ == "linux")
+ return host_os_release_linux ();
+ else
+ return nullopt;
+ }
+}
diff --git a/bpkg/host-os-release.hxx b/bpkg/host-os-release.hxx
new file mode 100644
index 0000000..c2d6672
--- /dev/null
+++ b/bpkg/host-os-release.hxx
@@ -0,0 +1,57 @@
+// file : bpkg/host-os-release.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_HOST_OS_RELEASE_HXX
+#define BPKG_HOST_OS_RELEASE_HXX
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+namespace bpkg
+{
+ // Information extracted from /etc/os-release on Linux. See os-release(5)
+ // for background. For other platforms we derive the equivalent information
+ // from other sources. Some examples:
+ //
+ // {"debian", {}, "10", "",
+ // "Debian GNU/Linux", "buster", ""}
+ //
+ // {"fedora", {}, "35", "workstation",
+ // "Fedora Linux", "", "Workstation Edition"}
+ //
+ // {"ubuntu", {"debian"}, "20.04", "",
+ // "Ubuntu", "focal", ""}
+ //
+ // {"windows", {}, "10", "",
+ // "Windows", "", ""}
+ //
+ // Note that version_id may be empty, for example, on Debian testing:
+ //
+ // {"debian", {}, "", "",
+ // "Debian GNU/Linux", "", ""}
+ //
+ // Note also that we don't extract PRETTY_NAME because its content is
+ // unpredictable. For example, it may include variant, as in "Fedora Linux
+ // 35 (Workstation Edition)". Instead, construct it from the individual
+ // components as appropriate, normally "$name $version ($version_codename)".
+ //
+ struct os_release
+ {
+ string name_id; // ID
+ vector<string> like_ids; // ID_LIKE
+ string version_id; // VERSION_ID
+ string variant_id; // VARIANT_ID
+
+ string name; // NAME
+ string version_codename; // VERSION_CODENAME
+ string variant; // VARIANT
+ };
+
+ // Return the release information for the specified host or nullopt if
+ // the specific host is unknown/unsupported.
+ //
+ optional<os_release>
+ host_os_release (const target_triplet& host);
+}
+
+#endif // BPKG_HOST_OS_RELEASE_HXX
diff --git a/bpkg/host-os-release.test.cxx b/bpkg/host-os-release.test.cxx
new file mode 100644
index 0000000..e65c3ca
--- /dev/null
+++ b/bpkg/host-os-release.test.cxx
@@ -0,0 +1,55 @@
+// file : bpkg/host-os-release.test.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <bpkg/host-os-release.hxx>
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <iostream>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+
+namespace bpkg
+{
+ extern os_release
+ host_os_release_linux (path f = {});
+
+ int
+ main (int argc, char* argv[])
+ {
+ assert (argc >= 2); // <host-target-triplet>
+
+ target_triplet host (argv[1]);
+
+ os_release r;
+ if (host.class_ == "linux")
+ {
+ assert (argc == 3); // <host-target-triplet> <file-path>
+ r = host_os_release_linux (path (argv[2]));
+ }
+ else
+ assert (false);
+
+ cout << r.name_id << '\n';
+ for (auto b (r.like_ids.begin ()), i (b); i != r.like_ids.end (); ++i)
+ cout << (i != b ? "|" : "") << *i;
+ cout << '\n'
+ << r.version_id << '\n'
+ << r.variant_id << '\n'
+ << r.name << '\n'
+ << r.version_codename << '\n'
+ << r.variant << '\n';
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return bpkg::main (argc, argv);
+}
diff --git a/bpkg/host-os-release.test.testscript b/bpkg/host-os-release.test.testscript
new file mode 100644
index 0000000..b32c477
--- /dev/null
+++ b/bpkg/host-os-release.test.testscript
@@ -0,0 +1,178 @@
+# file : bpkg/host-os-release.test.testscript
+# license : MIT; see accompanying LICENSE file
+
+: linux
+:
+$* x86_64-linux-gnu os-release >>EOO
+ linux
+
+
+
+ Linux
+
+
+ EOO
+
+: debian-10
+:
+cat <<EOI >=os-release;
+ PRETTY_NAME="Debian GNU/Linux 10 (buster)"
+ NAME="Debian GNU/Linux"
+ VERSION_ID="10"
+ VERSION="10 (buster)"
+ VERSION_CODENAME=buster
+ ID=debian
+ HOME_URL="https://www.debian.org/"
+ SUPPORT_URL="https://www.debian.org/support"
+ BUG_REPORT_URL="https://bugs.debian.org/"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ debian
+
+ 10
+
+ Debian GNU/Linux
+ buster
+
+ EOO
+
+: debian-testing
+:
+cat <<EOI >=os-release;
+ PRETTY_NAME="Debian GNU/Linux bookworm/sid"
+ NAME="Debian GNU/Linux"
+ ID=debian
+ HOME_URL="https://www.debian.org/"
+ SUPPORT_URL="https://www.debian.org/support"
+ BUG_REPORT_URL="https://bugs.debian.org/"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ debian
+
+
+
+ Debian GNU/Linux
+
+
+ EOO
+
+: ubuntu-20.04
+:
+cat <<EOI >=os-release;
+ NAME="Ubuntu"
+ VERSION="20.04.1 LTS (Focal Fossa)"
+ ID=ubuntu
+ ID_LIKE=debian
+ PRETTY_NAME="Ubuntu 20.04.1 LTS"
+ VERSION_ID="20.04"
+ HOME_URL="https://www.ubuntu.com/"
+ SUPPORT_URL="https://help.ubuntu.com/"
+ BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
+ PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
+ VERSION_CODENAME=focal
+ UBUNTU_CODENAME=focal
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ ubuntu
+ debian
+ 20.04
+
+ Ubuntu
+ focal
+
+ EOO
+
+: fedora-35
+:
+cat <<EOI >=os-release;
+ NAME="Fedora Linux"
+ VERSION="35 (Workstation Edition)"
+ ID=fedora
+ VERSION_ID=35
+ VERSION_CODENAME=""
+ PLATFORM_ID="platform:f35"
+ PRETTY_NAME="Fedora Linux 35 (Workstation Edition)"
+ ANSI_COLOR="0;38;2;60;110;180"
+ LOGO=fedora-logo-icon
+ CPE_NAME="cpe:/o:fedoraproject:fedora:35"
+ HOME_URL="https://fedoraproject.org/"
+ DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/"
+ SUPPORT_URL="https://ask.fedoraproject.org/"
+ BUG_REPORT_URL="https://bugzilla.redhat.com/"
+ REDHAT_BUGZILLA_PRODUCT="Fedora"
+ REDHAT_BUGZILLA_PRODUCT_VERSION=35
+ REDHAT_SUPPORT_PRODUCT="Fedora"
+ REDHAT_SUPPORT_PRODUCT_VERSION=35
+ PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
+ VARIANT="Workstation Edition"
+ VARIANT_ID=workstation
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ fedora
+
+ 35
+ workstation
+ Fedora Linux
+
+ Workstation Edition
+ EOO
+
+: rhel-8.2
+:
+cat <<EOI >=os-release;
+ NAME="Red Hat Enterprise Linux"
+ VERSION="8.2 (Ootpa)"
+ ID="rhel"
+ ID_LIKE="fedora"
+ VERSION_ID="8.2"
+ PLATFORM_ID="platform:el8"
+ PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
+ ANSI_COLOR="0;31"
+ CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
+ HOME_URL="https://www.redhat.com/"
+ BUG_REPORT_URL="https://bugzilla.redhat.com/"
+
+ REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
+ REDHAT_BUGZILLA_PRODUCT_VERSION=8.2
+ REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
+ REDHAT_SUPPORT_PRODUCT_VERSION="8.2"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ rhel
+ fedora
+ 8.2
+
+ Red Hat Enterprise Linux
+
+
+ EOO
+
+: centos-8
+:
+cat <<EOI >=os-release;
+ NAME="CentOS Linux"
+ VERSION="8 (Core)"
+ ID="centos"
+ ID_LIKE="rhel fedora"
+ VERSION_ID="8"
+ PLATFORM_ID="platform:el8"
+ PRETTY_NAME="CentOS Linux 8 (Core)"
+ ANSI_COLOR="0;31"
+ CPE_NAME="cpe:/o:centos:centos:8"
+ HOME_URL="https://www.centos.org/"
+ BUG_REPORT_URL="https://bugs.centos.org/"
+
+ CENTOS_MANTISBT_PROJECT="CentOS-8"
+ CENTOS_MANTISBT_PROJECT_VERSION="8"
+ REDHAT_SUPPORT_PRODUCT="centos"
+ REDHAT_SUPPORT_PRODUCT_VERSION="8"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ centos
+ rhel|fedora
+ 8
+
+ CentOS Linux
+
+
+ EOO
diff --git a/bpkg/package-query.hxx b/bpkg/package-query.hxx
index 1919058..1fbedd7 100644
--- a/bpkg/package-query.hxx
+++ b/bpkg/package-query.hxx
@@ -182,6 +182,8 @@ namespace bpkg
// Note that we return (loaded) lazy_shared_ptr in order to also convey
// the database to which it belongs.
//
+ // @@ Add available_packages typedef and use everywhere.
+ //
vector<pair<shared_ptr<available_package>,
lazy_shared_ptr<repository_fragment>>>
find_available_all (const linked_databases&,
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index b35fe0c..14b0852 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -1708,6 +1708,9 @@ namespace bpkg
//
//system_package_manager& spm (**sys_pkg_mgr);
+ // @@ TODO: if find_available_all() returns empty list, then this is
+ // an error (no source/stub for the package). Issue diag similar
+ // to other cases where we suggest specifing /*.
//@@ TODO: if we extracted a version, then we need to add an entry
// to the imaginary stubs (probably checking for duplicated), just
diff --git a/bpkg/system-package-manager.cxx b/bpkg/system-package-manager.cxx
index 1503c32..aa9bed8 100644
--- a/bpkg/system-package-manager.cxx
+++ b/bpkg/system-package-manager.cxx
@@ -15,14 +15,33 @@ namespace bpkg
unique_ptr<system_package_manager>
make_system_package_manager (const target_triplet& host,
- const string& type)
+ const string& name)
{
unique_ptr<system_package_manager> r;
+ if (optional<os_release> osr = host_os_release (host))
+ {
+ 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 ())
+ {
+ // @@ TODO: verify name if specified.
+
+ //r.reset (new system_package_manager_debian (move (*osr)));
+ }
+ }
+ }
+
if (r == nullptr)
{
- if (!type.empty ())
- fail << "unsupported package manager type '" << type << "' for host "
+ if (!name.empty ())
+ fail << "unsupported package manager '" << name << "' for host "
<< host;
}
diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx
index 122b181..4f01d1d 100644
--- a/bpkg/system-package-manager.hxx
+++ b/bpkg/system-package-manager.hxx
@@ -4,12 +4,14 @@
#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_HXX
#define BPKG_SYSTEM_PACKAGE_MANAGER_HXX
-//#include <libbpkg/manifest.hxx> // version
-//#include <libbpkg/package-name.hxx>
+#include <libbpkg/manifest.hxx> // version
+#include <libbpkg/package-name.hxx>
#include <bpkg/types.hxx>
#include <bpkg/utility.hxx>
+#include <bpkg/host-os-release.hxx>
+
namespace bpkg
{
// The system package manager interface. Used by both pkg-build (to query
@@ -18,17 +20,80 @@ namespace bpkg
class system_package_manager
{
public:
+ struct package_status
+ {
+ bpkg::version version;
+
+ // The system package can be either "available already installed"
+ // or "available not yet installed".
+ //
+ // If the package is partially installed (for example, libfoo but not
+ // libfoo-dev is installed), then installed should be false (and perhaps
+ // only a single available version should be returned).
+ //
+ bool installed;
+
+ string system_name;
+ string system_version;
+ };
+
+ // @@ We actually need to fetch is some are not installed to get their
+ // versions. We can do it as part of the call, no?
+
+ public:
virtual
~system_package_manager ();
+
+ explicit
+ system_package_manager (os_release&& osr)
+ : os_release_ (osr) {}
+
+ protected:
+ // 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".
+ //
+ // 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 of less than the specified version_id
+ // (include the value with the absent <version>). In a sense, absent
+ // <version> 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 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 the like_id's
+ // version and try that).
+ //
+ static vector<string>
+ system_package_names (/*const available_packages&,*/
+ const string& name_id,
+ const string& version_id,
+ const string& like_id);
+
+ protected:
+ os_release os_release_;
};
// Create a package manager instance corresponding to the specified host
- // target and optional manager type. If type is empty, return NULL if there
+ // target and optional manager name. If name is empty, return NULL if there
// is no support for this platform.
//
+ // @@ TODO: need to assign names. Ideas:
+ //
+ // dpkg-apt, rpm-dnf
+ // deb, rpm
+ // debian, fedora (i.e., follow /etc/os-release ID_LIKE lead)
+ //
unique_ptr<system_package_manager>
make_system_package_manager (const target_triplet&,
- const string& type);
+ const string& name);
}
#endif // BPKG_SYSTEM_PACKAGE_MANAGER_HXX