aboutsummaryrefslogtreecommitdiff
path: root/bpkg/pkg-status.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/pkg-status.cxx')
-rw-r--r--bpkg/pkg-status.cxx547
1 files changed, 385 insertions, 162 deletions
diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx
index 655ee8b..56503da 100644
--- a/bpkg/pkg-status.cxx
+++ b/bpkg/pkg-status.cxx
@@ -5,10 +5,13 @@
#include <iostream> // cout
+#include <libbutl/json/serializer.hxx>
+
#include <bpkg/package.hxx>
#include <bpkg/package-odb.hxx>
#include <bpkg/database.hxx>
#include <bpkg/diagnostics.hxx>
+#include <bpkg/package-query.hxx>
#include <bpkg/manifest-utility.hxx>
using namespace std;
@@ -18,6 +21,8 @@ namespace bpkg
{
struct package
{
+ database& pdb; // Package database.
+ database& rdb; // Repository info database.
package_name name;
bpkg::version version; // Empty if unspecified.
shared_ptr<selected_package> selected; // NULL if none selected.
@@ -25,100 +30,147 @@ namespace bpkg
};
using packages = vector<package>;
- // If recursive or immediate is true, then print status for dependencies
- // indented by two spaces.
- //
- static void
- pkg_status (const pkg_status_options& o,
- database& db,
- const packages& pkgs,
- string& indent,
- bool recursive,
- bool immediate)
+ struct available_package_status
{
- tracer trace ("pkg_status");
+ shared_ptr<available_package> package;
+
+ // Can only be built as a dependency.
+ //
+ // True if this package version doesn't belong to the repositories that
+ // were explicitly added to the configuration and their complements,
+ // recursively.
+ //
+ bool dependency;
+ };
- for (const package& p: pkgs)
- {
- l4 ([&]{trace << "package " << p.name << "; version " << p.version;});
+ class available_package_statuses: public vector<available_package_status>
+ {
+ public:
+ // Empty if the package is not available from the system. Can be `?`.
+ //
+ string system_package_version;
+
+ // Can only be built as a dependency.
+ //
+ // True if there are no package versions available from the repositories
+ // that were explicitly added to the configuration and their complements,
+ // recursively.
+ //
+ bool dependency = true;
+ };
- // Can't be both.
- //
- assert (p.version.empty () || !p.constraint);
+ static available_package_statuses
+ pkg_statuses (const pkg_status_options& o, const package& p)
+ {
+ database& rdb (p.rdb);
+ const shared_ptr<selected_package>& s (p.selected);
- const shared_ptr<selected_package>& s (p.selected);
+ available_package_statuses r;
+
+ bool known (false);
+
+ shared_ptr<repository_fragment> root (
+ rdb.load<repository_fragment> (""));
+
+ using query = query<available_package>;
+
+ query q (query::id.name == p.name);
+ {
+ auto qr (rdb.query<available_package> (q));
+ known = !qr.empty ();
+ r.dependency = (filter_one (root, move (qr)).first == nullptr);
+ }
- // Look for available packages.
+ if (known)
+ {
+ // If the user specified the version, then only look for that
+ // specific version (we still do it since there might be other
+ // revisions).
//
- // Some of them are only available to upgrade/downgrade as dependencies.
+ if (!p.version.empty ())
+ q = q && compare_version_eq (query::id.version,
+ canonical_version (p.version),
+ p.version.revision.has_value (),
+ false /* iteration */);
+
+ // And if we found an existing package, then only look for versions
+ // greater than to what already exists unless we were asked to show
+ // old versions.
//
- struct apkg
- {
- shared_ptr<available_package> package;
- bool build;
- };
- vector<apkg> apkgs;
-
- // A package with this name is known in available packages potentially
- // for build.
+ // Note that for a system wildcard version we will always show all
+ // available versions (since it is 0).
//
- bool known (false);
- bool build (false);
+ if (s != nullptr && !o.old_available ())
+ q = q && query::id.version > canonical_version (s->version);
+
+ q += order_by_version_desc (query::id.version);
+
+ for (shared_ptr<available_package> ap:
+ pointer_result (rdb.query<available_package> (q)))
{
- shared_ptr<repository_fragment> root (
- db.load<repository_fragment> (""));
+ bool dependency (filter (root, ap) == nullptr);
+ r.push_back (available_package_status {move (ap), dependency});
+ }
- using query = query<available_package>;
+ // The idea is that in the future we will try to auto-discover a system
+ // version. For now we just say "maybe available from the system" even
+ // if the version was specified by the user. We will later compare it if
+ // the user did specify the version.
+ //
+ if (o.system ())
+ r.system_package_version = '?';
- query q (query::id.name == p.name);
+ // Get rid of stubs.
+ //
+ for (auto i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->package->stub ())
{
- auto r (db.query<available_package> (q));
- known = !r.empty ();
- build = filter_one (root, move (r)).first != nullptr;
+ // All the rest are stubs so bail out.
+ //
+ r.erase (i, r.end ());
+ break;
}
+ }
+ }
- if (known)
- {
- // If the user specified the version, then only look for that
- // specific version (we still do it since there might be other
- // revisions).
- //
- if (!p.version.empty ())
- q = q && compare_version_eq (query::id.version,
- canonical_version (p.version),
- p.version.revision.has_value (),
- false /* iteration */);
+ return r;
+ }
- // And if we found an existing package, then only look for versions
- // greater than to what already exists unless we were asked to show
- // old versions.
- //
- // Note that for a system wildcard version we will always show all
- // available versions (since it is 0).
- //
- if (s != nullptr && !o.old_available ())
- q = q && query::id.version > canonical_version (s->version);
+ static packages
+ pkg_prerequisites (const shared_ptr<selected_package>& s, database& rdb)
+ {
+ packages r;
+ for (const auto& pair: s->prerequisites)
+ {
+ shared_ptr<selected_package> d (pair.first.load ());
+ database& db (pair.first.database ());
+ const optional<version_constraint>& c (pair.second.constraint);
+ r.push_back (package {db, rdb, d->name, version (), move (d), c});
+ }
+ return r;
+ }
+
+ static void
+ pkg_status_lines (const pkg_status_options& o,
+ const packages& pkgs,
+ string& indent,
+ bool recursive,
+ bool immediate)
+ {
+ tracer trace ("pkg_status_lines");
- q += order_by_version_desc (query::id.version);
+ for (const package& p: pkgs)
+ {
+ l4 ([&]{trace << "package " << p.name << "; version " << p.version;});
- // Packages that are in repositories that were explicitly added to
- // the configuration and their complements, recursively, are also
- // available to build.
- //
- for (shared_ptr<available_package> ap:
- pointer_result (
- db.query<available_package> (q)))
- {
- bool build (filter (root, ap));
- apkgs.push_back (apkg {move (ap), build});
- }
- }
- }
+ available_package_statuses ps (pkg_statuses (o, p));
cout << indent;
// Selected.
//
+ const shared_ptr<selected_package>& s (p.selected);
// Hold package status.
//
@@ -130,7 +182,7 @@ namespace bpkg
// If the package name is selected, then print its exact spelling.
//
- cout << (s != nullptr ? s->name : p.name);
+ cout << (s != nullptr ? s->name : p.name) << p.pdb;
if (o.constraint () && p.constraint)
cout << ' ' << *p.constraint;
@@ -154,105 +206,211 @@ namespace bpkg
// Available.
//
- bool available (false);
- if (known)
+ if (!ps.empty () || !ps.system_package_version.empty ())
{
- // Available from the system.
- //
- // The idea is that in the future we will try to auto-discover a
- // system version and then print that. For now we just say "maybe
- // available from the system" even if the version was specified by
- // the user. We will later compare it if the user did specify the
- // version.
- //
- string sys;
- if (o.system ())
+ cout << (s != nullptr ? " " : "") << "available";
+
+ for (const available_package_status& a: ps)
{
- sys = "?";
- available = true;
+ const version& v (a.package->version);
+
+ // Show the currently selected version in parenthesis.
+ //
+ bool cur (s != nullptr && v == s->version);
+
+ cout << ' '
+ << (cur ? "(" : a.dependency ? "[" : "")
+ << v
+ << (cur ? ")" : a.dependency ? "]" : "");
}
- // Get rid of stubs.
+ if (!ps.system_package_version.empty ())
+ cout << ' '
+ << (ps.dependency ? "[" : "")
+ << "sys:" << ps.system_package_version
+ << (ps.dependency ? "]" : "");
+ }
+ //
+ // Unknown.
+ //
+ else if (s == nullptr)
+ {
+ cout << "unknown";
+
+ // Print the user's version if specified.
//
- for (auto i (apkgs.begin ()); i != apkgs.end (); ++i)
+ if (!p.version.empty ())
+ cout << ' ' << p.version;
+ }
+
+ cout << endl;
+
+ if (recursive || immediate)
+ {
+ // Collect and recurse.
+ //
+ // Let's propagate the repository information source database from the
+ // dependent to its prerequisites.
+ //
+ if (s != nullptr)
{
- if (i->package->stub ())
+ packages dpkgs (pkg_prerequisites (s, p.rdb));
+
+ if (!dpkgs.empty ())
{
- // All the rest are stubs so bail out.
- //
- apkgs.erase (i, apkgs.end ());
- break;
+ indent += " ";
+ pkg_status_lines (o, dpkgs, indent, recursive, false /* immediate */);
+ indent.resize (indent.size () - 2);
}
+ }
+ }
+ }
+ }
+
+ static void
+ pkg_status_json (const pkg_status_options& o,
+ const packages& pkgs,
+ json::stream_serializer& ss,
+ bool recursive,
+ bool immediate)
+ {
+ tracer trace ("pkg_status_json");
+
+ ss.begin_array ();
+
+ for (const package& p: pkgs)
+ {
+ l4 ([&]{trace << "package " << p.name << "; version " << p.version;});
+
+ available_package_statuses ps (pkg_statuses (o, p));
+
+ const shared_ptr<selected_package>& s (p.selected);
+
+ // Note that we won't check some values for being valid UTF-8 (package
+ // names, etc), since their characters belong to even stricter character
+ // sets.
+ //
+ ss.begin_object ();
+
+ // If the package name is selected, then print its exact spelling.
+ //
+ ss.member ("name",
+ (s != nullptr ? s->name : p.name).string (),
+ false /* check */);
+
+ if (!p.pdb.string.empty ())
+ ss.member ("configuration", p.pdb.string);
+
+ if (o.constraint () && p.constraint)
+ ss.member ("constraint", p.constraint->string (), false /* check */);
+
+ // Selected.
+ //
+ if (s != nullptr)
+ {
+ ss.member ("status", to_string (s->state), false /* check */);
+
+ if (s->substate != package_substate::none)
+ ss.member ("sub_status", to_string (s->substate), false /* check */);
- available = true;
+ ss.member ("version", s->version_string (), false /* check */);
+
+ if (s->hold_package)
+ ss.member ("hold_package", true);
+
+ if (s->hold_version)
+ ss.member ("hold_version", true);
+ }
+
+ // Available.
+ //
+ if (!ps.empty () || !ps.system_package_version.empty ())
+ {
+ if (s == nullptr)
+ {
+ ss.member ("status", "available", false /* check */);
+
+ // Print the user's version if specified.
+ //
+ if (!p.version.empty ())
+ ss.member ("version", p.version.string (), false /* check */);
}
- if (available)
+ // Print the list of available versions, unless a specific available
+ // version is already printed.
+ //
+ if (s != nullptr || p.version.empty ())
{
- cout << (s != nullptr ? " " : "") << "available";
+ ss.member_name ("available_versions", false /* check */);
- for (const apkg& a: apkgs)
+ // Serialize an available package version.
+ //
+ auto serialize = [&ss] (const string& v, bool s, bool d)
{
- const version& v (a.package->version);
+ ss.begin_object ();
- // Show the currently selected version in parenthesis.
- //
- bool cur (s != nullptr && v == s->version);
+ ss.member ("version", v, false /* check */);
- cout << ' '
- << (cur ? "(" : a.build ? "" : "[")
- << v
- << (cur ? ")" : a.build ? "" : "]");
- }
+ if (s)
+ ss.member ("system", s);
+
+ if (d)
+ ss.member ("dependency", d);
- if (!sys.empty ())
- cout << ' '
- << (build ? "" : "[")
- << "sys:" << sys
- << (build ? "" : "]");
+ ss.end_object ();
+ };
+
+ ss.begin_array ();
+
+ for (const available_package_status& a: ps)
+ serialize (a.package->version.string (),
+ false /* system */,
+ a.dependency);
+
+ if (!ps.system_package_version.empty ())
+ serialize (ps.system_package_version,
+ true /* system */,
+ ps.dependency);
+
+ ss.end_array ();
}
}
-
- if (s == nullptr && !available)
+ //
+ // Unknown.
+ //
+ else if (s == nullptr)
{
- cout << "unknown";
+ ss.member ("status", "unknown", false /* check */);
// Print the user's version if specified.
//
if (!p.version.empty ())
- cout << ' ' << p.version;
+ ss.member ("version", p.version.string (), false /* check */);
}
- cout << endl;
-
if (recursive || immediate)
{
// Collect and recurse.
//
- packages dpkgs;
+ // Let's propagate the repository information source database from the
+ // dependent to its prerequisites.
+ //
if (s != nullptr)
{
- for (const auto& pair: s->prerequisites)
+ packages dpkgs (pkg_prerequisites (s, p.rdb));
+
+ if (!dpkgs.empty ())
{
- shared_ptr<selected_package> d (pair.first.load ());
- const optional<version_constraint>& c (pair.second);
- dpkgs.push_back (package {d->name, version (), move (d), c});
+ ss.member_name ("dependencies", false /* check */);
+ pkg_status_json (o, dpkgs, ss, recursive, false /* immediate */);
}
}
-
- if (!dpkgs.empty ())
- {
- indent += " ";
- pkg_status (o,
- db,
- dpkgs,
- indent,
- recursive,
- false /* immediate */);
- indent.resize (indent.size () - 2);
- }
}
+
+ ss.end_object ();
}
+
+ ss.end_array ();
}
int
@@ -266,10 +424,17 @@ namespace bpkg
const dir_path& c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
+ // Let's use as repository information source the package database for the
+ // held packages and the current database for the dependency packages.
+ //
+ // For the dependency packages we should probably use their dependent held
+ // package configurations recursively, but feels a bit hairy at the
+ // moment. So let's keep it simple for now. @@ TODO.
+ //
packages pkgs;
{
using query = query<selected_package>;
@@ -279,51 +444,109 @@ namespace bpkg
while (args.more ())
{
const char* arg (args.next ());
- package p {parse_package_name (arg),
- parse_package_version (arg,
- false /* allow_wildcard */,
- false /* fold_zero_revision */),
- nullptr /* selected */,
- nullopt /* constraint */};
-
- // Search in the packages that already exist in this configuration.
- //
- {
- query q (query::name == p.name);
- if (!p.version.empty ())
- q = q && compare_version_eq (query::version,
- canonical_version (p.version),
- p.version.revision.has_value (),
- false /* iteration */);
+ package_name pn (parse_package_name (arg));
+ version pv (parse_package_version (arg,
+ false /* allow_wildcard */,
+ version::none));
+
+ query q (query::name == pn);
- p.selected = db.query_one<selected_package> (q);
+ if (!pv.empty ())
+ q = q && compare_version_eq (query::version,
+ canonical_version (pv),
+ pv.revision.has_value (),
+ false /* iteration */);
+
+ // Search in the packages that already exist in this and all the
+ // dependency configurations.
+ //
+ bool found (false);
+ for (database& ldb: db.dependency_configs ())
+ {
+ shared_ptr<selected_package> sp (
+ ldb.query_one<selected_package> (q));
+
+ if (sp != nullptr)
+ {
+ pkgs.push_back (package {ldb,
+ sp->hold_package ? ldb : db,
+ pn,
+ pv,
+ move (sp),
+ nullopt /* constraint */});
+ found = true;
+ }
}
- pkgs.push_back (move (p));
+ if (!found)
+ {
+ pkgs.push_back (package {db,
+ db,
+ move (pn),
+ move (pv),
+ nullptr /* selected */,
+ nullopt /* constraint */});
+ }
}
}
else
{
- // Find all held packages.
+ // Find held/all packages in this and, if --link specified, all the
+ // dependency configurations.
//
- for (shared_ptr<selected_package> s:
- pointer_result (
- db.query<selected_package> (query::hold_package)))
+ query q;
+
+ if (!o.all ())
+ q = query::hold_package;
+
+ for (database& ldb: db.dependency_configs ())
{
- pkgs.push_back (package {s->name, version (), move (s), nullopt});
+ for (shared_ptr<selected_package> s:
+ pointer_result (
+ ldb.query<selected_package> (q)))
+ {
+ pkgs.push_back (package {ldb,
+ s->hold_package ? ldb : db,
+ s->name,
+ version (),
+ move (s),
+ nullopt /* constraint */});
+ }
+
+ if (!o.link ())
+ break;
}
if (pkgs.empty ())
{
- info << "no held packages in the configuration";
+ if (o.all ())
+ info << "no packages in the configuration";
+ else
+ info << "no held packages in the configuration" <<
+ info << "use --all|-a to see status of all packages";
+
return 0;
}
}
}
- string indent;
- pkg_status (o, db, pkgs, indent, o.recursive (), o.immediate ());
+ switch (o.stdout_format ())
+ {
+ case stdout_format::lines:
+ {
+ string indent;
+ pkg_status_lines (o, pkgs, indent, o.recursive (), o.immediate ());
+ break;
+ }
+ case stdout_format::json:
+ {
+ json::stream_serializer s (cout);
+ pkg_status_json (o, pkgs, s, o.recursive (), o.immediate ());
+ cout << endl;
+ break;
+ }
+ }
t.commit ();
return 0;