aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-02-09 09:00:55 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-02-09 09:00:55 +0200
commitf1d08308522bbcbc655f6e55b4b84af9253d8679 (patch)
treebdfce6b84427691b2aec989c2b7ee4061cd018e7
parentd6500b9d7ee5cf68a7507f9d4d726ffb767d827a (diff)
Infrastructure work for binary distribution package generation
-rw-r--r--bpkg/package-query.hxx21
-rw-r--r--bpkg/pkg-build.cli19
-rw-r--r--bpkg/pkg-build.cxx16
-rw-r--r--bpkg/pkg-install.hxx1
-rw-r--r--bpkg/pkg-uninstall.hxx1
-rw-r--r--bpkg/pkg-update.hxx1
-rw-r--r--bpkg/system-package-manager-debian.cxx123
-rw-r--r--bpkg/system-package-manager-debian.hxx49
-rw-r--r--bpkg/system-package-manager-debian.test.cxx9
-rw-r--r--bpkg/system-package-manager-fedora.cxx79
-rw-r--r--bpkg/system-package-manager-fedora.hxx39
-rw-r--r--bpkg/system-package-manager-fedora.test.cxx9
-rw-r--r--bpkg/system-package-manager.cxx141
-rw-r--r--bpkg/system-package-manager.hxx89
-rw-r--r--bpkg/system-repository.hxx2
15 files changed, 475 insertions, 124 deletions
diff --git a/bpkg/package-query.hxx b/bpkg/package-query.hxx
index ee9b595..98bb7a0 100644
--- a/bpkg/package-query.hxx
+++ b/bpkg/package-query.hxx
@@ -154,7 +154,11 @@ namespace bpkg
bool revision = false);
// Try to find an available package corresponding to the specified selected
- // package and, if not found, return a transient one.
+ // package and, if not found, return a transient one. The search is
+ // performed in the ultimate dependent configurations of the selected
+ // package (see dependent_repo_configs() for details).
+ //
+ // NOTE: repo_configs needs to be filled prior to the function call.
//
shared_ptr<available_package>
find_available (const common_options&,
@@ -166,6 +170,8 @@ namespace bpkg
// left empty and that the returned repository fragment could be NULL if the
// package is an orphan.
//
+ // NOTE: repo_configs needs to be filled prior to the function call.
+ //
pair<shared_ptr<available_package>,
lazy_shared_ptr<repository_fragment>>
find_available_fragment (const common_options&,
@@ -191,11 +197,17 @@ namespace bpkg
// locations list is left empty and that the returned repository fragment
// could be NULL if the package is an orphan.
//
- // Note also that in our model we assume that make_available_fragment() is
+ // Note that the repository fragment is searched in the ultimate dependent
+ // configurations of the selected package (see dependent_repo_configs() for
+ // details).
+ //
+ // Also note that in our model we assume that make_available_fragment() is
// only called if there is no real available_package. This makes sure that
// if the package moves (e.g., from testing to stable), then we will be
// using stable to resolve its dependencies.
//
+ // NOTE: repo_configs needs to be filled prior to the function call.
+ //
pair<shared_ptr<available_package>,
lazy_shared_ptr<repository_fragment>>
make_available_fragment (const common_options&,
@@ -230,6 +242,11 @@ namespace bpkg
// Return the ultimate dependent configurations for packages in this
// configuration.
//
+ // Specifically, this is an intersection of all the dependent configurations
+ // for the specified configuration (see database::dependent_configs() for
+ // details) and configurations which contain repository information
+ // (repo_configs).
+ //
linked_databases
dependent_repo_configs (database&);
}
diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli
index 41a8432..28e4fa2 100644
--- a/bpkg/pkg-build.cli
+++ b/bpkg/pkg-build.cli
@@ -466,6 +466,25 @@ namespace bpkg
with the \cb{--sys-install} option."
}
+ string --sys-distribution
+ {
+ "<name>",
+ "Alternative system/distribution package manager to interact with. The
+ valid <name> values are \cb{debian} (Debian and alike, such as Ubuntu,
+ etc) and \cb{fedora} (Fedora and alike, such as RHEL, CentOS, etc).
+ Note that some package managers may only be supported when running on
+ certain host operating systems."
+ }
+
+ string --sys-architecture
+ {
+ "<name>",
+ "Alternative architecture to use when interacting with the system
+ package manager. The valid <name> values are system/distribution
+ package manager-specific. If unspecified, the host architecture
+ is used."
+ }
+
dir_paths --directory|-d
{
"<dir>",
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index 9e5e1bb..6a4a4c3 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -1688,14 +1688,14 @@ namespace bpkg
if (!sys_pkg_mgr)
sys_pkg_mgr = o.sys_no_query ()
? nullptr
- : make_system_package_manager (o,
- host_triplet,
- o.sys_install (),
- !o.sys_no_fetch (),
- o.sys_yes (),
- o.sys_sudo (),
- "" /* name */);
-
+ : make_consumption_system_package_manager (o,
+ host_triplet,
+ o.sys_distribution (),
+ o.sys_architecture (),
+ o.sys_install (),
+ !o.sys_no_fetch (),
+ o.sys_yes (),
+ o.sys_sudo ());
if (*sys_pkg_mgr != nullptr)
{
system_package_manager& spm (**sys_pkg_mgr);
diff --git a/bpkg/pkg-install.hxx b/bpkg/pkg-install.hxx
index 3898c1a..3f257f0 100644
--- a/bpkg/pkg-install.hxx
+++ b/bpkg/pkg-install.hxx
@@ -5,7 +5,6 @@
#define BPKG_PKG_INSTALL_HXX
#include <bpkg/types.hxx>
-#include <bpkg/forward.hxx> // selected_package
#include <bpkg/utility.hxx>
#include <bpkg/pkg-command.hxx>
diff --git a/bpkg/pkg-uninstall.hxx b/bpkg/pkg-uninstall.hxx
index c3df29a..6024fe1 100644
--- a/bpkg/pkg-uninstall.hxx
+++ b/bpkg/pkg-uninstall.hxx
@@ -5,7 +5,6 @@
#define BPKG_PKG_UNINSTALL_HXX
#include <bpkg/types.hxx>
-#include <bpkg/forward.hxx> // selected_package
#include <bpkg/utility.hxx>
#include <bpkg/pkg-command.hxx>
diff --git a/bpkg/pkg-update.hxx b/bpkg/pkg-update.hxx
index 3a2df8c..cac7651 100644
--- a/bpkg/pkg-update.hxx
+++ b/bpkg/pkg-update.hxx
@@ -5,7 +5,6 @@
#define BPKG_PKG_UPDATE_HXX
#include <bpkg/types.hxx>
-#include <bpkg/forward.hxx> // selected_package
#include <bpkg/utility.hxx>
#include <bpkg/pkg-command.hxx>
diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx
index 4cad141..b131851 100644
--- a/bpkg/system-package-manager-debian.cxx
+++ b/bpkg/system-package-manager-debian.cxx
@@ -11,6 +11,19 @@ namespace bpkg
{
using package_status = system_package_status_debian;
+ // Translate host CPU to Debian package architecture.
+ //
+ string system_package_manager_debian::
+ arch_from_target (const target_triplet& h)
+ {
+ const string& c (h.cpu);
+ return
+ c == "x86_64" ? "amd64" :
+ c == "aarch64" ? "arm64" :
+ c == "i386" || c == "i486" || c == "i586" || c == "i686" ? "i386" :
+ c;
+ }
+
// Parse the debian-name (or alike) value.
//
// Note that for now we treat all the packages from the non-main groups as
@@ -757,7 +770,7 @@ namespace bpkg
if (verb >= 2)
print_process (args);
else if (verb == 1)
- text << "updating " << os_release_.name_id << " package index...";
+ text << "updating " << os_release.name_id << " package index...";
process pr;
if (!simulate_)
@@ -781,7 +794,7 @@ namespace bpkg
}
if (verb == 1)
- text << "updated " << os_release_.name_id << " package index";
+ text << "updated " << os_release.name_id << " package index";
}
catch (const process_error& e)
{
@@ -817,7 +830,7 @@ namespace bpkg
if (verb >= 2)
print_process (args);
else if (verb == 1)
- text << "installing " << os_release_.name_id << " packages...";
+ text << "installing " << os_release.name_id << " packages...";
process pr;
if (!simulate_)
@@ -844,7 +857,7 @@ namespace bpkg
}
if (verb == 1)
- text << "installed " << os_release_.name_id << " packages";
+ text << "installed " << os_release.name_id << " packages";
}
catch (const process_error& e)
{
@@ -889,15 +902,15 @@ namespace bpkg
[this, &pn] (diag_record& dr)
{
dr << info << "while mapping " << pn << " to "
- << os_release_.name_id << " package name";
+ << os_release.name_id << " package name";
});
strings ns;
if (!aps->empty ())
ns = system_package_names (*aps,
- os_release_.name_id,
- os_release_.version_id,
- os_release_.like_ids);
+ 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
@@ -970,7 +983,7 @@ namespace bpkg
if (s.main.empty ())
{
- fail << "unable to guess main " << os_release_.name_id
+ fail << "unable to guess main " << os_release.name_id
<< " package for " << s.dev << ' ' << ver <<
info << s.dev << " Depends value: " << depends <<
info << "consider specifying explicit mapping in " << pn
@@ -1073,7 +1086,7 @@ namespace bpkg
if (dr.empty ())
{
- dr << fail << "multiple installed " << os_release_.name_id
+ dr << fail << "multiple installed " << os_release.name_id
<< " packages for " << pn <<
info << "candidate: " << r->main << " " << r->system_version;
}
@@ -1172,7 +1185,7 @@ namespace bpkg
if (dr.empty ())
{
dr << fail << "multiple partially installed "
- << os_release_.name_id << " packages for " << pn;
+ << os_release.name_id << " packages for " << pn;
dr << info << "candidate: " << r->main << " " << r->system_version
<< ", missing components:";
@@ -1208,7 +1221,7 @@ namespace bpkg
if (dr.empty ())
{
- dr << fail << "multiple available " << os_release_.name_id
+ dr << fail << "multiple available " << os_release.name_id
<< " packages for " << pn <<
info << "candidate: " << r->main << " " << r->system_version;
}
@@ -1238,9 +1251,9 @@ namespace bpkg
if (!aps->empty ())
v = downstream_package_version (sv,
*aps,
- os_release_.name_id,
- os_release_.version_id,
- os_release_.like_ids);
+ os_release.name_id,
+ os_release.version_id,
+ os_release.like_ids);
if (!v)
{
@@ -1257,10 +1270,10 @@ namespace bpkg
}
catch (const invalid_argument& e)
{
- fail << "unable to map " << os_release_.name_id << " package "
+ fail << "unable to map " << os_release.name_id << " package "
<< r->system_name << " version " << sv << " to bpkg package "
<< pn << " version" <<
- info << os_release_.name_id << " version is not a valid bpkg "
+ info << os_release.name_id << " version is not a valid bpkg "
<< "version: " << e.what () <<
info << "consider specifying explicit mapping in " << pn
<< " package manifest";
@@ -1403,7 +1416,7 @@ namespace bpkg
if (pp.installed_version != ps.system_version)
{
- fail << "unexpected " << os_release_.name_id << " package version "
+ fail << "unexpected " << os_release.name_id << " package version "
<< "for " << ps.system_name <<
info << "expected: " << ps.system_version <<
info << "installed: " << pp.installed_version <<
@@ -1412,4 +1425,78 @@ namespace bpkg
}
}
}
+
+ // Some background on creating Debian packages (for a bit more detailed
+ // overview see the Debian Packaging Tutorial).
+ //
+ // A binary Debian package (.deb) is an ar archive which itself contains a
+ // few tar archives normally compressed with gz or xz. So it's possible to
+ // create the package completely manually without using any of the Debian
+ // tools and while some implementations (for example, cargo-deb) do it this
+ // way, we are not going to go this route because it does not scale well to
+ // more complex packages which may require additional functionality, such as
+ // managing systemd files, and which is covered by the Debian tools (for an
+ // example of where this leads, see the partial debhelper re-implementation
+ // in cargo-deb). Another issues with this approach is that it's not
+ // amenable to customizations, at least not in a way familiar to Debian
+ // users.
+ //
+ // At the lowest level of the Debian tools for creating packages sits the
+ // dpkg-deb --build|-b command (also accessible as dpkg --build|-b). Given a
+ // directory with all the binary contents (including the package metadata,
+ // such as the control file, in the debian/ subdirectory) this command will
+ // pack everything up into a .deb file. While an improvement over the fully
+ // manual packaging, this approach has essentially the same drawbacks. In
+ // particular, this command generates a single package which means we will
+ // have to manually sort out things into -dev, -doc, etc.
+ //
+ // Next up the stack is dpkg-buildpackage. This tool expects the package to
+ // follow the Debian way, that is, to provide the debian/rules makefile with
+ // a number of required targets which it then invokes to build, install, and
+ // pack a package from source (and somewhere in this process it calls
+ // dpkg-deb --build). The dpkg-buildpackage(1) man page has an overview of
+ // all the steps that this command performs and it is the recommended,
+ // lower-level, way to build packages on Debian.
+ //
+ // At the top of the stack sits debuild which calls dpkg-buildpackage, then
+ // lintian and finally design (though signing can also be performed by
+ // dpkg-buildpackage).
+ //
+ // Based on this our plan is to use dpkg-buildpackage which brings us to the
+ // Debian way of packaging with debian/rules at its core. As it turns out,
+ // it can also be implemented in a number of alternative ways. So let's
+ // discuss those.
+ //
+ // As mentioned earlier, debian/rules is a makefile that is expected to
+ // provide a number of targets, such as build, install, etc. And
+ // theoretically these targets can be implemented completely manually. In
+ // practice, however, the Debian way is to use the debhelper(1) packaging
+ // helper tools. For example, there are helpers for stripping binaries,
+ // compressing man pages, fixing permissions, and managing systemd files.
+ //
+ // While debhelper tools definitely simplify debian/rules, there is often
+ // still a lot of boilerplate code. So second-level helpers are often used,
+ // with the dominant option being the dh(1) command sequencer (there is also
+ // CDBS but it appears to be mostly obsolete).
+ //
+ // Based on that our options appear to be classic debhelper and dh. Looking
+ // at the statistics, it's clear that the majority of packages (including
+ // fairly complex ones) tend to prefer dh and there is no reason for us to
+ // try to buck this trend.
+ //
+ // So, to sum up, the plan is to produce debian/rules that uses the dh
+ // command sequencer and then invoke dpkg-buildpackage to produce the binary
+ // package from that. While this approach is normally used to build things
+ // from source, it feels like we should be able to pretend that we are by,
+ // for example, overriding the install target to invoke the build system to
+ // install all the packages directly from their bpkg locations.
+ //
+ void system_package_manager_debian::
+ generate (packages&&,
+ packages&&,
+ strings&&,
+ const dir_path&,
+ optional<recursive_mode>)
+ {
+ }
}
diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx
index 9fb93c7..e01b25d 100644
--- a/bpkg/system-package-manager-debian.hxx
+++ b/bpkg/system-package-manager-debian.hxx
@@ -14,7 +14,9 @@
namespace bpkg
{
// The system package manager implementation for Debian and alike (Ubuntu,
- // etc) using the APT frontend.
+ // 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.
//
@@ -26,7 +28,7 @@ namespace bpkg
// (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:
//
- // libz3-4 libz3-dev
+ // libsqlite3-0 libsqlite3-dev
//
// libssl1.1 libssl-dev libssl-doc
// libssl3 libssl-dev libssl-doc
@@ -34,6 +36,9 @@ namespace bpkg
// 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).
+ //
// 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
@@ -124,11 +129,44 @@ namespace bpkg
virtual void
pkg_install (const vector<package_name>&) override;
+ virtual void
+ generate (packages&&,
+ packages&&,
+ strings&&,
+ const dir_path&,
+ optional<recursive_mode>) override;
+
public:
- // Expects os_release::name_id to be "debian" or os_release::like_ids to
+ // Expect os_release::name_id to be "debian" or os_release::like_ids to
// contain "debian".
//
- using system_package_manager::system_package_manager;
+ // @@ 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,
+ bool install,
+ bool fetch,
+ bool yes,
+ string sudo)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress,
+ install,
+ fetch,
+ yes,
+ move (sudo)) {}
+
+ system_package_manager_debian (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress) {}
// Implementation details exposed for testing (see definitions for
// documentation).
@@ -158,6 +196,9 @@ namespace bpkg
static string
main_from_dev (const string&, const string&, const string&);
+ static string
+ arch_from_target (const target_triplet&);
+
// 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
diff --git a/bpkg/system-package-manager-debian.test.cxx b/bpkg/system-package-manager-debian.test.cxx
index a033400..d719860 100644
--- a/bpkg/system-package-manager-debian.test.cxx
+++ b/bpkg/system-package-manager-debian.test.cxx
@@ -90,9 +90,10 @@ namespace bpkg
system_package_manager_debian m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
false /* install */,
false /* fetch */,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
@@ -117,9 +118,10 @@ namespace bpkg
system_package_manager_debian m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
false /* install */,
false /* fetch */,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
@@ -283,9 +285,10 @@ namespace bpkg
system_package_manager_debian m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
install,
fetch,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx
index 7e6990e..b117eab 100644
--- a/bpkg/system-package-manager-fedora.cxx
+++ b/bpkg/system-package-manager-fedora.cxx
@@ -11,6 +11,17 @@ namespace bpkg
{
using package_status = system_package_status_fedora;
+ // Translate host CPU to Fedora package architecture.
+ //
+ string system_package_manager_fedora::
+ arch_from_target (const target_triplet& h)
+ {
+ const string& c (h.cpu);
+ return
+ c == "i386" || c == "i486" || c == "i586" || c == "i686" ? "i686" :
+ c;
+ }
+
// Parse the fedora-name (or alike) value.
//
// Note that for now we treat all the packages from the non-main groups as
@@ -413,9 +424,7 @@ namespace bpkg
// Skip the package if its architecture differs from the host
// architecture.
//
- // @@ TODO: arch translation.
- //
- if (a != host_.cpu && a != "noarch")
+ if (a != arch && a != "noarch")
continue;
p.resize (e);
@@ -537,7 +546,7 @@ namespace bpkg
vector<pair<string, string>> system_package_manager_fedora::
dnf_repoquery_requires (const string& name,
const string& ver,
- const string& arch)
+ const string& qarch)
{
assert (!name.empty () && !ver.empty () && !arch.empty ());
@@ -547,7 +556,7 @@ namespace bpkg
// dependencies with different architecture (see the below example). It
// feels sensible to just skip them.
//
- string spec (name + '-' + ver + '.' + arch);
+ string spec (name + '-' + ver + '.' + qarch);
// The --quiet option makes sure we don't get 'Last metadata expiration
// check: <timestamp>' printed to stderr. It does not appear to affect
@@ -593,7 +602,7 @@ namespace bpkg
evars);
else
{
- simulation::package k {name, ver, arch};
+ simulation::package k {name, ver, qarch};
const path* f (nullptr);
if (fetched_)
@@ -685,7 +694,7 @@ namespace bpkg
// Skip a potential self-dependency and dependencies of a different
// architecture.
//
- if (p == name || (a != host_.cpu && a != "noarch"))
+ if (p == name || (a != arch && a != "noarch"))
continue;
r.emplace_back (move (p), move (v));
@@ -821,8 +830,7 @@ namespace bpkg
if (verb >= 2)
print_process (args);
else if (verb == 1)
- text << "updating " << os_release_.name_id
- << " repositories metadata...";
+ text << "updating " << os_release.name_id << " repositories metadata...";
process pr;
if (!simulate_)
@@ -846,7 +854,7 @@ namespace bpkg
}
if (verb == 1)
- text << "updated " << os_release_.name_id << " repositories metadata";
+ text << "updated " << os_release.name_id << " repositories metadata";
}
catch (const process_error& e)
{
@@ -902,7 +910,7 @@ namespace bpkg
if (verb >= 2)
print_process (args);
else if (verb == 1)
- text << "installing " << os_release_.name_id << " packages...";
+ text << "installing " << os_release.name_id << " packages...";
process pr;
if (!simulate_)
@@ -985,7 +993,7 @@ namespace bpkg
}
if (verb == 1)
- text << "installed " << os_release_.name_id << " packages";
+ text << "installed " << os_release.name_id << " packages";
}
catch (const process_error& e)
{
@@ -1031,16 +1039,16 @@ namespace bpkg
auto df = make_diag_frame (
[this, &pn] (diag_record& dr)
{
- dr << info << "while mapping " << pn << " to "
- << os_release_.name_id << " package name";
+ dr << info << "while mapping " << pn << " to " << os_release.name_id
+ << " package name";
});
strings ns;
if (!aps->empty ())
ns = system_package_names (*aps,
- os_release_.name_id,
- os_release_.version_id,
- os_release_.like_ids);
+ os_release.name_id,
+ os_release.version_id,
+ os_release.like_ids);
if (ns.empty ())
{
// Attempt to automatically translate our package name. Failed that we
@@ -1134,17 +1142,17 @@ namespace bpkg
//
auto guess_main = [this, &pn] (package_status& s,
const string& ver,
- const string& arch)
+ const string& qarch)
{
vector<pair<string, string>> depends (
- dnf_repoquery_requires (s.devel, ver, arch));
+ dnf_repoquery_requires (s.devel, ver, qarch));
s.main = main_from_devel (s.devel, ver, depends);
if (s.main.empty ())
{
diag_record dr (fail);
- dr << "unable to guess main " << os_release_.name_id
+ dr << "unable to guess main " << os_release.name_id
<< " package for " << s.devel << ' ' << ver <<
info << "depends on";
@@ -1259,9 +1267,9 @@ namespace bpkg
// found. Double-check Debian semantics.
//
fail << "unable to guess " << (devel ? "devel" : "main")
- << ' ' << os_release_.name_id << " package for " << pn <<
+ << ' ' << os_release.name_id << " package for " << pn <<
info << "neither " << name << " nor " << ps.fallback
- << ' ' << os_release_.name_id << " package exists" <<
+ << ' ' << os_release.name_id << " package exists" <<
info << "consider specifying explicit mapping in " << pn
<< " package manifest";
}
@@ -1323,7 +1331,7 @@ namespace bpkg
if (dr.empty ())
{
- dr << fail << "multiple installed " << os_release_.name_id
+ dr << fail << "multiple installed " << os_release.name_id
<< " packages for " << pn <<
info << "candidate: " << r->main << " " << r->system_version;
}
@@ -1423,7 +1431,7 @@ namespace bpkg
if (dr.empty ())
{
dr << fail << "multiple partially installed "
- << os_release_.name_id << " packages for " << pn;
+ << os_release.name_id << " packages for " << pn;
dr << info << "candidate: " << r->main << " " << r->system_version
<< ", missing components:";
@@ -1459,7 +1467,7 @@ namespace bpkg
if (dr.empty ())
{
- dr << fail << "multiple available " << os_release_.name_id
+ dr << fail << "multiple available " << os_release.name_id
<< " packages for " << pn <<
info << "candidate: " << r->main << " " << r->system_version;
}
@@ -1488,9 +1496,9 @@ namespace bpkg
if (!aps->empty ())
v = downstream_package_version (sv,
*aps,
- os_release_.name_id,
- os_release_.version_id,
- os_release_.like_ids);
+ os_release.name_id,
+ os_release.version_id,
+ os_release.like_ids);
if (!v)
{
@@ -1507,10 +1515,10 @@ namespace bpkg
}
catch (const invalid_argument& e)
{
- fail << "unable to map " << os_release_.name_id << " package "
+ fail << "unable to map " << os_release.name_id << " package "
<< r->system_name << " version " << sv << " to bpkg package "
<< pn << " version" <<
- info << os_release_.name_id << " version is not a valid bpkg "
+ info << os_release.name_id << " version is not a valid bpkg "
<< "version: " << e.what () <<
info << "consider specifying explicit mapping in " << pn
<< " package manifest";
@@ -1653,7 +1661,7 @@ namespace bpkg
if (pi.installed_version != ps.system_version)
{
- fail << "unexpected " << os_release_.name_id << " package version "
+ fail << "unexpected " << os_release.name_id << " package version "
<< "for " << ps.system_name <<
info << "expected: " << ps.system_version <<
info << "installed: " << pi.installed_version <<
@@ -1662,4 +1670,13 @@ namespace bpkg
}
}
}
+
+ void system_package_manager_fedora::
+ generate (packages&&,
+ packages&&,
+ strings&&,
+ const dir_path&,
+ optional<recursive_mode>)
+ {
+ }
}
diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx
index 16fe9a3..52d2a3f 100644
--- a/bpkg/system-package-manager-fedora.hxx
+++ b/bpkg/system-package-manager-fedora.hxx
@@ -205,10 +205,42 @@ namespace bpkg
virtual void
pkg_install (const vector<package_name>&) override;
+ virtual void
+ generate (packages&&,
+ packages&&,
+ strings&&,
+ const dir_path&,
+ optional<recursive_mode>) override;
+
public:
- // Expects os_release::name_id to be "fedora" or os_release::like_ids to
+ // Expect os_release::name_id to be "fedora" or os_release::like_ids to
// contain "fedora".
- using system_package_manager::system_package_manager;
+ //
+ system_package_manager_fedora (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress,
+ bool install,
+ bool fetch,
+ bool yes,
+ string sudo)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress,
+ install,
+ fetch,
+ yes,
+ move (sudo)) {}
+
+ system_package_manager_fedora (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress)
+ : system_package_manager (move (osr),
+ h,
+ a.empty () ? arch_from_target (h) : move (a),
+ progress) {}
// Implementation details exposed for testing (see definitions for
// documentation).
@@ -240,6 +272,9 @@ namespace bpkg
const string&,
const vector<pair<string, string>>&);
+ static string
+ arch_from_target (const target_triplet&);
+
// If simulate is not NULL, then instead of executing the actual dnf
// commands simulate their execution: (1) for `dnf list` and `dnf
// repoquery --requires` by printing their command lines and reading the
diff --git a/bpkg/system-package-manager-fedora.test.cxx b/bpkg/system-package-manager-fedora.test.cxx
index 2c5a976..8064d19 100644
--- a/bpkg/system-package-manager-fedora.test.cxx
+++ b/bpkg/system-package-manager-fedora.test.cxx
@@ -100,9 +100,10 @@ namespace bpkg
system_package_manager_fedora m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
false /* install */,
false /* fetch */,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
@@ -129,9 +130,10 @@ namespace bpkg
system_package_manager_fedora m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
false /* install */,
false /* fetch */,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
@@ -317,9 +319,10 @@ namespace bpkg
system_package_manager_fedora m (move (osr),
host_triplet,
+ "" /* arch */,
+ nullopt /* progress */,
install,
fetch,
- nullopt /* progress */,
false /* yes */,
"sudo");
m.simulate_ = &s;
diff --git a/bpkg/system-package-manager.cxx b/bpkg/system-package-manager.cxx
index 6a44b09..caf951f 100644
--- a/bpkg/system-package-manager.cxx
+++ b/bpkg/system-package-manager.cxx
@@ -27,70 +27,147 @@ namespace bpkg
// vtable
}
+ // Return true if the specified operating system is or like the specified
+ // id.
+ //
+ static inline bool
+ is_or_like (const os_release& os, const char* id)
+ {
+ return (os.name_id == id ||
+ find_if (os.like_ids.begin (), os.like_ids.end (),
+ [id] (const string& n)
+ {
+ return n == id;
+ }) != os.like_ids.end ());
+ }
+
unique_ptr<system_package_manager>
- make_system_package_manager (const common_options& co,
- const target_triplet& host,
- bool install,
- bool fetch,
- bool yes,
- const string& sudo,
- const string& name)
+ make_consumption_system_package_manager (const common_options& co,
+ const target_triplet& host,
+ const string& name,
+ const string& arch,
+ bool install,
+ bool fetch,
+ bool yes,
+ const string& sudo)
{
+ // Note: similar to make_consumption_system_package_manager() below.
+
optional<bool> progress (co.progress () ? true :
co.no_progress () ? false :
optional<bool> ());
unique_ptr<system_package_manager> r;
- if (optional<os_release> osr = host_os_release (host))
+ if (optional<os_release> oos = 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 ());
- };
+ os_release& os (*oos);
if (host.class_ == "linux")
{
- if (is_or_like ("debian") ||
- is_or_like ("ubuntu"))
+ if (is_or_like (os, "debian") ||
+ is_or_like (os, "ubuntu"))
{
if (!name.empty () && name != "debian")
fail << "unsupported package manager '" << name << "' for "
- << osr->name_id << " host";
+ << os.name_id << " host";
// 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");
+ if (os.name_id != "debian" && !is_or_like (os, "debian"))
+ os.like_ids.push_back ("debian");
r.reset (new system_package_manager_debian (
- move (*osr), host, install, fetch, progress, yes, sudo));
+ move (os), host, arch,
+ progress, install, fetch, yes, sudo));
}
- else if (is_or_like ("fedora") ||
- is_or_like ("rhel") ||
- is_or_like ("centos") ||
- is_or_like ("rocky") ||
- is_or_like ("almalinux"))
+ else if (is_or_like (os, "fedora") ||
+ is_or_like (os, "rhel") ||
+ is_or_like (os, "centos") ||
+ is_or_like (os, "rocky") ||
+ is_or_like (os, "almalinux"))
{
if (!name.empty () && name != "fedora")
fail << "unsupported package manager '" << name << "' for "
- << osr->name_id << " host";
+ << os.name_id << " host";
// 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");
+ if (os.name_id != "fedora" && !is_or_like (os, "fedora"))
+ os.like_ids.push_back ("fedora");
+
+ r.reset (new system_package_manager_fedora (
+ move (os), host, arch,
+ progress, install, fetch, yes, sudo));
+ }
+ // NOTE: remember to update the --sys-distribution pkg-build option
+ // documentation if adding support for another package manager.
+ }
+ }
+
+ if (r == nullptr)
+ {
+ if (!name.empty ())
+ fail << "unsupported package manager '" << name << "' for host "
+ << host;
+ }
+
+ return r;
+ }
+
+ unique_ptr<system_package_manager>
+ make_production_system_package_manager (const common_options& co,
+ const target_triplet& host,
+ const string& name,
+ const string& arch)
+ {
+ // Note: similar to make_production_system_package_manager() above.
+
+ optional<bool> progress (co.progress () ? true :
+ co.no_progress () ? false :
+ optional<bool> ());
+
+ unique_ptr<system_package_manager> r;
+
+ if (optional<os_release> oos = host_os_release (host))
+ {
+ os_release& os (*oos);
+
+ if (host.class_ == "linux")
+ {
+ if (is_or_like (os, "debian") ||
+ is_or_like (os, "ubuntu"))
+ {
+ if (!name.empty () && name != "debian")
+ fail << "unsupported package manager '" << name << "' for "
+ << os.name_id << " host";
+
+ if (os.name_id != "debian" && !is_or_like (os, "debian"))
+ os.like_ids.push_back ("debian");
+
+ r.reset (new system_package_manager_debian (
+ move (os), host, arch, progress));
+ }
+ else if (is_or_like (os, "fedora") ||
+ is_or_like (os, "rhel") ||
+ is_or_like (os, "centos") ||
+ is_or_like (os, "rocky") ||
+ is_or_like (os, "almalinux"))
+ {
+ if (!name.empty () && name != "fedora")
+ fail << "unsupported package manager '" << name << "' for "
+ << os.name_id << " host";
+
+ if (os.name_id != "fedora" && !is_or_like (os, "fedora"))
+ os.like_ids.push_back ("fedora");
r.reset (new system_package_manager_fedora (
- move (*osr), host, install, fetch, progress, yes, sudo));
+ move (os), host, arch, progress));
}
+ // NOTE: remember to update the --distribution pkg-bindist option
+ // documentation if adding support for another package manager.
}
}
diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx
index 9a9c443..63bf676 100644
--- a/bpkg/system-package-manager.hxx
+++ b/bpkg/system-package-manager.hxx
@@ -17,7 +17,7 @@
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
+ // (to query and install system packages) and by pkg-bindist (to generate
// them).
//
// Note that currently the result of a query is a single available version.
@@ -103,6 +103,13 @@ namespace bpkg
status_type status = not_installed;
};
+ // As mentioned above the system package manager API has two parts:
+ // consumption (status() and install()) and production (generate()) and a
+ // particular implementation may only implement one, the other, or both. If
+ // a particular part is not implemented, then the correponding make_*()
+ // function below should never return an instance of such a system package
+ // manager.
+ //
class system_package_manager
{
public:
@@ -148,7 +155,32 @@ namespace bpkg
virtual void
pkg_install (const vector<package_name>&) = 0;
+ // Generate a binary distribution package.
+ //
+ // @@ TODO: doc
+ //
+ // See the pkg-bindist(1) man page and the pkg_bindist() function
+ // implementation for background and details.
+ //
+ using packages =
+ vector<pair<shared_ptr<selected_package>, available_packages>>;
+
+ enum class recursive_mode {auto_, full};
+
+ virtual void
+ generate (packages&& pkgs,
+ packages&& deps,
+ strings&& vars,
+ const dir_path& out,
+ optional<recursive_mode>) = 0;
+
public:
+ bpkg::os_release os_release;
+ target_triplet host;
+ string arch; // Architecture in system package manager spelling.
+
+ // Consumption constructor.
+ //
// If install is true, then enable package installation.
//
// If fetch is false, then do not re-fetch the system package repository
@@ -156,21 +188,37 @@ namespace bpkg
// available version of the not yet installed or partially installed
// packages.
//
- system_package_manager (os_release&& osr,
- const target_triplet& host,
+ system_package_manager (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress,
bool install,
bool fetch,
- optional<bool> progress,
bool yes,
string sudo)
- : os_release_ (osr),
- host_ (host),
+ : os_release (move (osr)),
+ host (h),
+ arch (move (a)),
progress_ (progress),
install_ (install),
fetch_ (fetch),
yes_ (yes),
sudo_ (sudo != "false" ? move (sudo) : string ()) {}
+ // Production constructor.
+ //
+ system_package_manager (bpkg::os_release&& osr,
+ const target_triplet& h,
+ string a,
+ optional<bool> progress)
+ : os_release (move (osr)),
+ host (h),
+ arch (move (a)),
+ progress_ (progress),
+ install_ (false),
+ fetch_ (false),
+ yes_ (false) {}
+
virtual
~system_package_manager ();
@@ -235,8 +283,6 @@ namespace bpkg
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.
@@ -248,8 +294,10 @@ namespace bpkg
};
// 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:
+ // target triplet as well as optional distribution package manager name and
+ // architecture. If name is empty, return NULL if there is no support for
+ // this platform. If architecture is empty, then derive it automatically
+ // from the host target triplet. Currently recognized names:
//
// debian -- Debian and alike (Ubuntu, etc) using the APT frontend.
// fedora -- Fedora and alike (RHEL, Centos, etc) using the DNF frontend.
@@ -258,13 +306,20 @@ namespace bpkg
// 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);
+ make_consumption_system_package_manager (const common_options&,
+ const target_triplet&,
+ const string& name,
+ const string& arch,
+ bool install,
+ bool fetch,
+ bool yes,
+ const string& sudo);
+
+ unique_ptr<system_package_manager>
+ make_production_system_package_manager (const common_options&,
+ const target_triplet&,
+ const string& name,
+ const string& arch);
}
#endif // BPKG_SYSTEM_PACKAGE_MANAGER_HXX
diff --git a/bpkg/system-repository.hxx b/bpkg/system-repository.hxx
index 31e14d1..6fc04f9 100644
--- a/bpkg/system-repository.hxx
+++ b/bpkg/system-repository.hxx
@@ -18,7 +18,7 @@ namespace bpkg
{
// A map of discovered system package versions. The information can be
// authoritative (i.e., it was provided by the user or auto-discovered
- // on this run) or non-authoritative (i.e., comes from selected_packages
+ // on this run) or non-authoritative (i.e., comes from selected packages
// that are present in the database; in a sence it was authoritative but
// on some previous run.
//