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.cxx437
1 files changed, 302 insertions, 135 deletions
diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx
index 2475fa1..93008a8 100644
--- a/bpkg/pkg-status.cxx
+++ b/bpkg/pkg-status.cxx
@@ -5,6 +5,8 @@
#include <iostream> // cout
+#include <libbutl/json/serializer.hxx>
+
#include <bpkg/package.hxx>
#include <bpkg/package-odb.hxx>
#include <bpkg/database.hxx>
@@ -27,102 +29,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,
- const packages& pkgs,
- string& indent,
- bool recursive,
- bool immediate)
+ struct available_package_status
{
- tracer trace ("pkg_status");
+ shared_ptr<available_package> package;
- for (const package& p: pkgs)
- {
- l4 ([&]{trace << "package " << p.name << "; version " << p.version;});
+ // 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;
+ };
+
+ 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;
+ };
- database& pdb (p.pdb);
- database& rdb (p.rdb);
+ 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);
- // Can't be both.
- //
- assert (p.version.empty () || !p.constraint);
+ available_package_statuses r;
- const shared_ptr<selected_package>& s (p.selected);
+ bool known (false);
+
+ shared_ptr<repository_fragment> root (
+ rdb.load<repository_fragment> (""));
- // Look for available packages.
+ 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);
+ }
+
+ 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 (
- rdb.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 (rdb.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);
+ r.push_back (package {db, rdb, d->name, version (), move (d), c});
+ }
+ return r;
+ }
- q += order_by_version_desc (query::id.version);
+ static void
+ pkg_status_lines (const pkg_status_options& o,
+ const packages& pkgs,
+ string& indent,
+ bool recursive,
+ bool immediate)
+ {
+ tracer trace ("pkg_status_lines");
- // 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 (
- rdb.query<available_package> (q)))
- {
- bool build (filter (root, ap));
- apkgs.push_back (apkg {move (ap), build});
- }
- }
- }
+ for (const package& p: pkgs)
+ {
+ l4 ([&]{trace << "package " << p.name << "; version " << p.version;});
+
+ available_package_statuses ps (pkg_statuses (o, p));
cout << indent;
// Selected.
//
+ const shared_ptr<selected_package>& s (p.selected);
// Hold package status.
//
@@ -134,7 +181,7 @@ namespace bpkg
// If the package name is selected, then print its exact spelling.
//
- cout << (s != nullptr ? s->name : p.name) << pdb;
+ cout << (s != nullptr ? s->name : p.name) << p.pdb;
if (o.constraint () && p.constraint)
cout << ' ' << *p.constraint;
@@ -158,77 +205,188 @@ 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 */);
+
+ 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 */);
- available = true;
+ // 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");
- 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);
+
+ 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);
- if (!sys.empty ())
- cout << ' '
- << (build ? "" : "[")
- << "sys:" << sys
- << (build ? "" : "]");
+ 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.
@@ -236,27 +394,22 @@ namespace bpkg
// Let's propagate the repository information source database from the
// dependent to its prerequisites.
//
- packages dpkgs;
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 ());
- database& db (pair.first.database ());
- const optional<version_constraint>& c (pair.second);
- dpkgs.push_back (
- package {db, rdb, d->name, version (), move (d), c});
+ ss.member_name ("dependencies");
+ pkg_status_json (o, dpkgs, ss, recursive, false /* immediate */);
}
}
-
- if (!dpkgs.empty ())
- {
- indent += " ";
- pkg_status (o, dpkgs, indent, recursive, false /* immediate */);
- indent.resize (indent.size () - 2);
- }
}
+
+ ss.end_object ();
}
+
+ ss.end_array ();
}
int
@@ -377,8 +530,22 @@ namespace bpkg
}
}
- string indent;
- pkg_status (o, 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;