From 8ff6314283396a60ae9806a03f1c017bdc3ec4cc Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 4 Mar 2022 17:19:18 +0300 Subject: Add support for --stdout-format to pkg-status command --- bpkg/pkg-status.cxx | 437 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 302 insertions(+), 135 deletions(-) (limited to 'bpkg/pkg-status.cxx') 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 // cout +#include + #include #include #include @@ -27,102 +29,147 @@ namespace bpkg }; using packages = vector; - // 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 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 + { + 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& s (p.selected); - // Can't be both. - // - assert (p.version.empty () || !p.constraint); + available_package_statuses r; - const shared_ptr& s (p.selected); + bool known (false); + + shared_ptr root ( + rdb.load ("")); - // Look for available packages. + using query = query; + + query q (query::id.name == p.name); + { + auto qr (rdb.query (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 package; - bool build; - }; - vector 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 ap: + pointer_result (rdb.query (q))) { - shared_ptr root ( - rdb.load ("")); + bool dependency (filter (root, ap) == nullptr); + r.push_back (available_package_status {move (ap), dependency}); + } - using query = query; + // 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 (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& s, database& rdb) + { + packages r; + for (const auto& pair: s->prerequisites) + { + shared_ptr d (pair.first.load ()); + database& db (pair.first.database ()); + const optional& 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 ap: - pointer_result ( - rdb.query (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& 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& 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 d (pair.first.load ()); - database& db (pair.first.database ()); - const optional& 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; -- cgit v1.1