aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager-debian.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/system-package-manager-debian.cxx')
-rw-r--r--bpkg/system-package-manager-debian.cxx370
1 files changed, 227 insertions, 143 deletions
diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx
index f885e2d..a7da2c0 100644
--- a/bpkg/system-package-manager-debian.cxx
+++ b/bpkg/system-package-manager-debian.cxx
@@ -5,12 +5,11 @@
#include <bpkg/diagnostics.hxx>
+using namespace butl;
+
namespace bpkg
{
- using package_status = system_package_status;
- using package_status_debian = system_package_status_debian;
-
- using package_policy = package_status_debian::package_policy;
+ using package_status = system_package_status_debian;
// Parse the debian-name (or alike) value.
//
@@ -45,7 +44,7 @@ namespace bpkg
// main package) as well as -doc and -dbg unless requested (the
// extra_{doc,dbg} arguments).
//
- static package_status_debian
+ static package_status
parse_debian_name (const string& nv,
bool extra_doc,
bool extra_dbg)
@@ -72,7 +71,7 @@ namespace bpkg
if (ns.empty ())
fail << "empty package group";
- package_status_debian r;
+ package_status r;
// Handle the dev instead of main special case for libraries.
//
@@ -87,10 +86,10 @@ namespace bpkg
suffix (m, "-dev") &&
!(ns.size () > 1 && suffix (ns[1], "-dev")))
{
- r = package_status_debian ("", move (m));
+ r = package_status ("", move (m));
}
else
- r = package_status_debian (move (m));
+ r = package_status (move (m));
}
// Handle the rest.
@@ -121,14 +120,14 @@ namespace bpkg
strings gs (split (nv, ','));
assert (!gs.empty ()); // *-name value cannot be empty.
- package_status_debian r;
+ package_status r;
for (size_t i (0); i != gs.size (); ++i)
{
if (i == 0) // Main group.
r = parse_group (gs[i]);
else
{
- package_status_debian g (parse_group (gs[i]));
+ package_status g (parse_group (gs[i]));
if (!g.main.empty ()) r.extras.push_back (move (g.main));
if (!g.dev.empty ()) r.extras.push_back (move (g.dev));
@@ -145,6 +144,60 @@ namespace bpkg
return r;
}
+ // Attempt to determine the main package name from its -dev package based on
+ // the extracted Depends value. Return empty string if unable to.
+ //
+ string system_package_manager_debian::
+ main_from_dev (const string& dev_name,
+ const string& dev_ver,
+ const string& depends)
+ {
+ // The format of the Depends value is a comma-seperated list of dependency
+ // expressions. For example:
+ //
+ // Depends: libssl3 (= 3.0.7-1), libc6 (>= 2.34), libfoo | libbar
+ //
+ // For the main package we look for a dependency in the form:
+ //
+ // <dev-stem>* (= <dev-ver>)
+ //
+ // Usually it is the first one.
+ //
+ string dev_stem (dev_name, 0, dev_name.rfind ("-dev"));
+
+ string r;
+ for (size_t b (0), e (0); next_word (depends, b, e, ','); )
+ {
+ string d (depends, b, e - b);
+ trim (d);
+
+ size_t p (d.find (' '));
+ if (p != string::npos)
+ {
+ if (d.compare (0, dev_stem.size (), dev_stem) == 0) // <dev-stem>*
+ {
+ size_t q (d.find ('(', p + 1));
+ if (q != string::npos && d.back () == ')') // (...)
+ {
+ if (d[q + 1] == '=' && d[q + 2] == ' ') // Equal.
+ {
+ string v (d, q + 3, d.size () - q - 3 - 1);
+ trim (v);
+
+ if (v == dev_ver)
+ {
+ r.assign (d, 0, p);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return r;
+ }
+
// Do we use apt or apt-get? From apt(8):
//
// "The apt(8) commandline is designed as an end-user tool and it may change
@@ -155,17 +208,20 @@ namespace bpkg
// these commands (potentially with some additional options enabled) in
// your scripts as they keep backward compatibility as much as possible."
//
+ // Note also that for some reason both apt-cache and apt-get exit with 100
+ // code on error.
+ //
static process_path apt_cache_path;
static process_path apt_get_path;
static process_path sudo_path;
- // Obtain the installed and candidate versions for the specified list
- // of Debian packages by executing `apt-cache policy`.
+ // Obtain the installed and candidate versions for the specified list of
+ // Debian packages by executing `apt-cache policy`.
//
// If the n argument is not 0, then only query the first n packages.
//
- static void
- apt_cache_policy (vector<package_policy>& pps, size_t n = 0)
+ void system_package_manager_debian::
+ apt_cache_policy (vector<package_policy>& pps, size_t n)
{
if (n == 0)
n = pps.size ();
@@ -202,7 +258,7 @@ namespace bpkg
try
{
- if (apt_cache_path.empty ())
+ if (apt_cache_path.empty () && !simulate_)
apt_cache_path = process::path_search (args[0]);
process_env pe (apt_cache_path, evars);
@@ -213,13 +269,51 @@ namespace bpkg
// Redirect stdout to a pipe. For good measure also redirect stdin to
// /dev/null to make sure there are no prompts of any kind.
//
- process pr (apt_cache_path,
- args,
- -2 /* stdin */,
- -1 /* stdout */,
- 2 /* stderr */,
- nullptr /* cwd */,
- evars);
+ process pr;
+ if (!simulate_)
+ pr = process (apt_cache_path,
+ args,
+ -2 /* stdin */,
+ -1 /* stdout */,
+ 2 /* stderr */,
+ nullptr /* cwd */,
+ evars);
+ else
+ {
+ print_process (pe, args);
+
+ strings k;
+ for (size_t i (0); i != n; ++i)
+ k.push_back (pps[i].name);
+
+ const path* f (nullptr);
+ if (installed_)
+ {
+ auto i (simulate_->apt_cache_policy_installed_.find (k));
+ if (i != simulate_->apt_cache_policy_installed_.end ())
+ f = &i->second;
+ }
+ if (f == nullptr && fetched_)
+ {
+ auto i (simulate_->apt_cache_policy_fetched_.find (k));
+ if (i != simulate_->apt_cache_policy_fetched_.end ())
+ f = &i->second;
+ }
+ if (f == nullptr)
+ {
+ auto i (simulate_->apt_cache_policy_.find (k));
+ if (i != simulate_->apt_cache_policy_.end ())
+ f = &i->second;
+ }
+
+ pr = process (process_exit (0));
+ pr.in_ofd = f == nullptr || f->empty ()
+ ? fdopen_null ()
+ : (f->string () == "-"
+ ? fddup (stdin_fd ())
+ : fdopen (*f, fdopen_mode::in));
+ }
+
try
{
ifdstream is (move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit);
@@ -270,7 +364,7 @@ namespace bpkg
// Skip until this package.
//
- for (; i != n && pps[i].name.get () != l; ++i) ;
+ for (; i != n && pps[i].name != l; ++i) ;
if (i == n)
fail << "unexpected package name '" << l << "'";
@@ -310,9 +404,14 @@ namespace bpkg
pp.candidate_version = parse_version ("Candidate");
+ // Candidate should fallback to Installed.
+ //
+ assert (pp.installed_version.empty () ||
+ !pp.candidate_version.empty ());
+
// Skip the rest of the indented lines (or blanks, just in case).
//
- while (!eof (getline (is, l)) && (l.empty () || l.front () != ' ')) ;
+ while (!eof (getline (is, l)) && (l.empty () || l.front () == ' ')) ;
}
}
@@ -353,7 +452,7 @@ namespace bpkg
// specified package and version. Fail if either package or version is
// unknown.
//
- static string
+ string system_package_manager_debian::
apt_cache_show (const string& name, const string& ver)
{
assert (!name.empty () && !ver.empty ());
@@ -377,7 +476,7 @@ namespace bpkg
string r;
try
{
- if (apt_cache_path.empty ())
+ if (apt_cache_path.empty () && !simulate_)
apt_cache_path = process::path_search (args[0]);
process_env pe (apt_cache_path, evars);
@@ -388,13 +487,48 @@ namespace bpkg
// Redirect stdout to a pipe. For good measure also redirect stdin to
// /dev/null to make sure there are no prompts of any kind.
//
- process pr (apt_cache_path,
- args,
- -2 /* stdin */,
- -1 /* stdout */,
- 2 /* stderr */,
- nullptr /* cwd */,
- evars);
+ process pr;
+ if (!simulate_)
+ pr = process (apt_cache_path,
+ args,
+ -2 /* stdin */,
+ -1 /* stdout */,
+ 2 /* stderr */,
+ nullptr /* cwd */,
+ evars);
+ else
+ {
+ print_process (pe, args);
+
+ pair<string, string> k (name, ver);
+
+ const path* f (nullptr);
+ if (fetched_)
+ {
+ auto i (simulate_->apt_cache_show_fetched_.find (k));
+ if (i != simulate_->apt_cache_show_fetched_.end ())
+ f = &i->second;
+ }
+ if (f == nullptr)
+ {
+ auto i (simulate_->apt_cache_show_.find (k));
+ if (i != simulate_->apt_cache_show_.end ())
+ f = &i->second;
+ }
+
+ if (f == nullptr || f->empty ())
+ {
+ text << "E: No packages found";
+ pr = process (process_exit (100));
+ }
+ else
+ {
+ pr = process (process_exit (0));
+ pr.in_ofd = f->string () == "-"
+ ? fddup (stdin_fd ())
+ : fdopen (*f, fdopen_mode::in);
+ }
+ }
bool no_version (false);
try
@@ -435,10 +569,13 @@ namespace bpkg
do
{
- // This line should be the start of a field unless it's a
- // comment. According to deb822(5), there can be no leading
- // whitespaces before `#`.
+ // This line should be the start of a field unless it's a comment
+ // or the terminating blank line. According to deb822(5), there
+ // can be no leading whitespaces before `#`.
//
+ if (l.empty ())
+ break;
+
if (l[0] == '#')
{
getline (is, l);
@@ -533,75 +670,15 @@ namespace bpkg
return r;
}
- // Attempt to determine the main package name from its -dev package. Return
- // empty string if unable. Save the extracted Depends value to the depends
- // argument for diagnostics.
- //
- static string
- main_from_dev (const string& dev_name,
- const string& dev_ver,
- string& depends)
- {
- depends = apt_cache_show (dev_name, dev_ver);
-
- // The format of the Depends value is a comma-seperated list of dependency
- // expressions. For example:
- //
- // Depends: libssl3 (= 3.0.7-1), libc6 (>= 2.34), libfoo | libbar
- //
- // For the main package we look for a dependency in the form:
- //
- // <dev-stem>* (= <dev-ver>)
- //
- // Usually it is the first one.
- //
- string dev_stem (dev_name, 0, dev_name.rfind ("-dev"));
-
- string r;
- for (size_t b (0), e (0); next_word (depends, b, e, ','); )
- {
- string d (depends, b, e - b);
- trim (d);
-
- size_t p (d.find (' '));
- if (p != string::npos)
- {
- if (d.compare (0, dev_stem.size (), dev_stem) == 0) // <dev-stem>*
- {
- size_t q (d.find ('(', p + 1));
- if (q != string::npos && d.back () == ')') // (...)
- {
- if (d[q + 1] == '=' && d[q + 2] == ' ') // Equal.
- {
- string v (d, q + 3, d.size () - q - 3 - 1);
- trim (v);
-
- if (v == dev_ver)
- {
- r.assign (d, 0, p);
- break;
- }
- }
- }
- }
- }
- }
-
- return r;
- }
-
// Prepare the common `apt-get <command>` options.
//
- static pair<cstrings, const process_path&>
- apt_get_common (const char* command,
- const string& sudo,
- optional<bool> progress,
- bool yes)
+ pair<cstrings, const process_path&> system_package_manager_debian::
+ apt_get_common (const char* command)
{
cstrings args;
- if (!sudo.empty ())
- args.push_back (sudo.c_str ());
+ if (!sudo_.empty ())
+ args.push_back (sudo_.c_str ());
args.push_back ("apt-get");
args.push_back (command);
@@ -621,7 +698,7 @@ namespace bpkg
// apt-get install it shows additionally progress during unpacking which
// looks quite odd.
//
- if (progress && *progress)
+ if (progress_ && *progress_)
{
args.push_back ("--quiet=0");
}
@@ -629,14 +706,14 @@ namespace bpkg
{
// Only use level 2 if assuming yes.
//
- args.push_back (yes ? "--quiet=2" : "--quiet");
+ args.push_back (yes_ ? "--quiet=2" : "--quiet");
}
- else if (progress && !*progress)
+ else if (progress_ && !*progress_)
{
args.push_back ("--quiet");
}
- if (yes)
+ if (yes_)
{
args.push_back ("--assume-yes");
}
@@ -651,16 +728,16 @@ namespace bpkg
{
const process_path* pp (nullptr);
- if (!sudo.empty ())
+ if (!sudo_.empty ())
{
- if (sudo_path.empty ())
+ if (sudo_path.empty () && !simulate_)
sudo_path = process::path_search (args[0]);
pp = &sudo_path;
}
else
{
- if (apt_get_path.empty ())
+ if (apt_get_path.empty () && !simulate_)
apt_get_path = process::path_search (args[0]);
pp = &apt_get_path;
@@ -681,11 +758,10 @@ namespace bpkg
// Execute `apt-get update` to update the package index.
//
- static void
- apt_get_update (const string& sudo, optional<bool> progress, bool yes)
+ void system_package_manager_debian::
+ apt_get_update ()
{
- pair<cstrings, const process_path&> args_pp (
- apt_get_common ("update", sudo, progress, yes));
+ pair<cstrings, const process_path&> args_pp (apt_get_common ("update"));
cstrings& args (args_pp.first);
const process_path& pp (args_pp.second);
@@ -699,16 +775,21 @@ namespace bpkg
else if (verb)
text << "updating Debian package index...";
- // We don't expect any prompts from apt-get update, but who knows.
- //
- process pr (pp, args);
+ process pr;
+ if (!simulate_)
+ pr = process (pp, args);
+ else
+ {
+ print_process (args);
+ pr = process (process_exit (simulate_->apt_get_update_fail_ ? 100 : 0));
+ }
if (!pr.wait ())
{
diag_record dr (fail);
dr << "apt-get update exited with non-zero code";
- if (verb < 3)
+ if (verb < 2)
{
dr << "command line: ";
print_process (dr, args);
@@ -726,19 +807,15 @@ namespace bpkg
}
}
- // Execute `apt-get install` to install the specified packages/version
+ // Execute `apt-get install` to install the specified packages/versions
// (e.g., libfoo or libfoo=1.2.3).
//
- static void
- apt_get_install (const strings& pkgs,
- const string& sudo,
- optional<bool> progress,
- bool yes)
+ void system_package_manager_debian::
+ apt_get_install (const strings& pkgs)
{
assert (!pkgs.empty ());
- pair<cstrings, const process_path&> args_pp (
- apt_get_common ("install", sudo, progress, yes));
+ pair<cstrings, const process_path&> args_pp (apt_get_common ("install"));
cstrings& args (args_pp.first);
const process_path& pp (args_pp.second);
@@ -755,14 +832,21 @@ namespace bpkg
else if (verb)
text << "installing Debian packages...";
- process pr (pp, args);
+ process pr;
+ if (!simulate_)
+ pr = process (pp, args);
+ else
+ {
+ print_process (args);
+ pr = process (process_exit (simulate_->apt_get_install_fail_ ? 100 : 0));
+ }
if (!pr.wait ())
{
diag_record dr (fail);
dr << "apt-get install exited with non-zero code";
- if (verb < 3)
+ if (verb < 2)
{
dr << "command line: ";
print_process (dr, args);
@@ -783,8 +867,7 @@ namespace bpkg
}
}
- optional<const system_package_status*>
- system_package_manager_debian::
+ optional<const system_package_status*> system_package_manager_debian::
pkg_status (const package_name& pn, const available_packages* aps)
{
// For now we ignore -doc and -dbg package components (but we may want to
@@ -806,7 +889,7 @@ namespace bpkg
return nullopt;
}
- vector<package_status_debian> candidates;
+ vector<package_status> candidates;
// Translate our package name to the Debian package names.
//
@@ -838,10 +921,10 @@ namespace bpkg
// Keep the main package name empty as an indication that it is to
// be discovered.
//
- candidates.push_back (package_status_debian ("", n + "-dev"));
+ candidates.push_back (package_status ("", n + "-dev"));
}
else
- candidates.push_back (package_status_debian (n));
+ candidates.push_back (package_status (n));
}
else
{
@@ -849,13 +932,13 @@ namespace bpkg
//
for (const string& n: ns)
{
- package_status_debian s (parse_debian_name (n, need_doc, need_dbg));
+ package_status s (parse_debian_name (n, need_doc, need_dbg));
// Suppress duplicates for good measure based on the main package
// name (and falling back to -dev if empty).
//
auto i (find_if (candidates.begin (), candidates.end (),
- [&s] (const package_status_debian& x)
+ [&s] (const package_status& x)
{
return s.main.empty ()
? s.dev == x.dev
@@ -873,9 +956,10 @@ namespace bpkg
// Guess unknown main package given the dev package and its version.
//
- auto guess_main = [&pn] (package_status_debian& s, const string& ver)
+ auto guess_main = [this, &pn] (package_status& s, const string& ver)
{
- string depends;
+ string depends (apt_cache_show (s.dev, ver));
+
s.main = main_from_dev (s.dev, ver, depends);
if (s.main.empty ())
@@ -924,9 +1008,9 @@ namespace bpkg
// First look for an already fully installed package.
//
- optional<package_status_debian> r;
+ optional<package_status> r;
- for (package_status_debian& ps: candidates)
+ for (package_status& ps: candidates)
{
vector<package_policy>& pps (ps.package_policies);
@@ -993,11 +1077,11 @@ namespace bpkg
bool requery;
if ((requery = fetch_ && !fetched_))
{
- apt_get_update (sudo_, progress_, yes_);
+ apt_get_update ();
fetched_ = true;
}
- for (package_status_debian& ps: candidates)
+ for (package_status& ps: candidates)
{
vector<package_policy>& pps (ps.package_policies);
@@ -1064,7 +1148,7 @@ namespace bpkg
if (!r)
{
- for (package_status_debian& ps: candidates)
+ for (package_status& ps: candidates)
{
if (ps.main.empty ())
continue;
@@ -1146,7 +1230,7 @@ namespace bpkg
auto it (status_cache_.find (pn));
assert (it != status_cache_.end () && it->second);
- const package_status_debian& ps (*it->second);
+ const package_status& ps (*it->second);
// At first it may seem we don't need to do anything for already fully
// installed packages. But it's possible some of them were automatically
@@ -1208,7 +1292,7 @@ namespace bpkg
specs.push_back (move (s));
}
- apt_get_install (specs, sudo_, progress_, yes_);
+ apt_get_install (specs);
}
// Verify that versions we have promised in pkg_status() match what
@@ -1219,7 +1303,7 @@ namespace bpkg
for (const package_name& pn: pns)
{
- const package_status_debian& ps (*status_cache_.find (pn)->second);
+ const package_status& ps (*status_cache_.find (pn)->second);
pps.push_back (package_policy (ps.system_name));
}
@@ -1228,7 +1312,7 @@ namespace bpkg
auto i (pps.begin ());
for (const package_name& pn: pns)
{
- const package_status_debian& ps (*status_cache_.find (pn)->second);
+ const package_status& ps (*status_cache_.find (pn)->second);
const package_policy& pp (*i++);
if (pp.installed_version != ps.system_version)