Utilities/pkg: Integrate LibSemVer for update information
Along with this, Port.h is include which helps generalising common information for the port package, like it's name and version. With SemVer complaint versions, it is possible to show positive change (upgrade) or negative change (downgrade) in the installed ports. However, for some non-complaint versions (eg. using git commit hash), non-equality (`!=`) is used to notify upgrade. Since there is no algorithm (without git history) to check the order of commits, it is not possible to inform whether it is an upgrade or downgrade.
This commit is contained in:
parent
ee639fa1df
commit
fc8f6c07b4
Notes:
sideshowbarker
2024-07-17 20:22:04 +09:00
Author: https://github.com/tbhaxor Commit: https://github.com/SerenityOS/serenity/commit/fc8f6c07b4 Pull-request: https://github.com/SerenityOS/serenity/pull/21904 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/gmta Reviewed-by: https://github.com/timschumi
7 changed files with 176 additions and 83 deletions
|
@ -28,44 +28,56 @@
|
|||
#include <Shell/PosixParser.h>
|
||||
#include <Shell/Shell.h>
|
||||
|
||||
static bool is_installed(HashMap<String, InstalledPort>& installed_ports_database, StringView package_name)
|
||||
void AvailablePort::query_details_for_package(HashMap<String, AvailablePort>& available_ports, HashMap<String, InstalledPort> const& installed_ports, StringView package_name, bool verbose)
|
||||
{
|
||||
auto port = installed_ports_database.find(package_name);
|
||||
return port != installed_ports_database.end();
|
||||
}
|
||||
|
||||
static Optional<AvailablePort&> find_port_package(HashMap<String, AvailablePort>& available_ports, StringView package_name)
|
||||
{
|
||||
auto port = available_ports.find(package_name);
|
||||
if (port == available_ports.end())
|
||||
return {};
|
||||
return port->value;
|
||||
}
|
||||
|
||||
ErrorOr<int> AvailablePort::query_details_for_package(HashMap<String, AvailablePort>& available_ports, HashMap<String, InstalledPort>& installed_ports, StringView package_name, bool verbose)
|
||||
{
|
||||
auto possible_available_port = find_port_package(available_ports, package_name);
|
||||
if (!possible_available_port.has_value()) {
|
||||
auto possible_available_port = available_ports.find(package_name);
|
||||
if (possible_available_port == available_ports.end()) {
|
||||
outln("pkg: No match for queried name \"{}\"", package_name);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& available_port = possible_available_port.release_value();
|
||||
auto& available_port = possible_available_port->value;
|
||||
|
||||
outln("{}: {}, {}", available_port.name(), available_port.version(), available_port.website());
|
||||
outln("{}: {}, {}", available_port.name(), available_port.version_string(), available_port.website());
|
||||
if (verbose) {
|
||||
out("Installed: ");
|
||||
if (is_installed(installed_ports, package_name))
|
||||
auto installed_port = installed_ports.find(package_name);
|
||||
if (installed_port != installed_ports.end()) {
|
||||
outln("Yes");
|
||||
else
|
||||
|
||||
out("Update Status: ");
|
||||
|
||||
auto error_or_available_version = available_port.version_semver();
|
||||
auto error_or_installed_version = installed_port->value.version_semver();
|
||||
|
||||
if (error_or_available_version.is_error() || error_or_installed_version.is_error()) {
|
||||
auto ip_version = installed_port->value.version_string();
|
||||
auto ap_version = available_port.version_string();
|
||||
|
||||
if (ip_version == ap_version) {
|
||||
outln("Already on latest version");
|
||||
} else {
|
||||
outln("Update to {} available", ap_version);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto available_version = error_or_available_version.value();
|
||||
auto installed_version = error_or_installed_version.value();
|
||||
if (available_version.is_same(installed_version, SemVer::CompareType::Patch)) {
|
||||
outln("Already on latest version");
|
||||
} else if (available_version.is_greater_than(installed_version)) {
|
||||
outln("Update to {} available", available_port.version_string());
|
||||
}
|
||||
} else {
|
||||
outln("No");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Optional<Markdown::Table::Column const&> get_column_in_table(Markdown::Table const& ports_table, StringView column_name)
|
||||
{
|
||||
for (auto& column : ports_table.columns()) {
|
||||
for (auto const& column : ports_table.columns()) {
|
||||
if (column_name == column.header.render_for_terminal())
|
||||
return column;
|
||||
}
|
||||
|
@ -151,9 +163,9 @@ ErrorOr<HashMap<String, AvailablePort>> AvailablePort::read_available_ports_list
|
|||
if (!possible_port_website_column.has_value())
|
||||
return Error::from_string_literal("pkg: Website column not found /usr/Ports/AvailablePorts.md");
|
||||
|
||||
auto& port_name_column = possible_port_name_column.release_value();
|
||||
auto& port_version_column = possible_port_version_column.release_value();
|
||||
auto& port_website_column = possible_port_website_column.release_value();
|
||||
auto const& port_name_column = possible_port_name_column.release_value();
|
||||
auto const& port_version_column = possible_port_version_column.release_value();
|
||||
auto const& port_website_column = possible_port_website_column.release_value();
|
||||
|
||||
VERIFY(port_name_column.rows.size() == port_version_column.rows.size());
|
||||
VERIFY(port_version_column.rows.size() == port_website_column.rows.size());
|
||||
|
|
|
@ -12,25 +12,20 @@
|
|||
#include <AK/StringView.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
class AvailablePort {
|
||||
class AvailablePort : public Port {
|
||||
public:
|
||||
static ErrorOr<int> query_details_for_package(HashMap<String, AvailablePort>& available_ports, HashMap<String, InstalledPort>& installed_ports, StringView package_name, bool verbose);
|
||||
static void query_details_for_package(HashMap<String, AvailablePort>& available_ports, HashMap<String, InstalledPort> const& installed_ports, StringView package_name, bool verbose);
|
||||
static ErrorOr<HashMap<String, AvailablePort>> read_available_ports_list();
|
||||
static ErrorOr<int> update_available_ports_list_file();
|
||||
|
||||
AvailablePort(String name, String version, String website)
|
||||
: m_name(name)
|
||||
, m_website(move(website))
|
||||
, m_version(move(version))
|
||||
AvailablePort(String const& name, String const& version, String const& website)
|
||||
: Port(name, version)
|
||||
, m_website(website)
|
||||
{
|
||||
}
|
||||
|
||||
StringView name() const { return m_name.bytes_as_string_view(); }
|
||||
StringView version() const { return m_version.bytes_as_string_view(); }
|
||||
StringView website() const { return m_website.bytes_as_string_view(); }
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
String m_website;
|
||||
String m_version;
|
||||
};
|
||||
|
|
|
@ -12,4 +12,4 @@ set(SOURCES
|
|||
)
|
||||
|
||||
serenity_app(PackageManager ICON app-assistant)
|
||||
target_link_libraries(PackageManager PRIVATE LibCore LibMain LibFileSystem LibProtocol LibHTTP LibMarkdown LibShell)
|
||||
target_link_libraries(PackageManager PRIVATE LibCore LibSemVer LibMain LibFileSystem LibProtocol LibHTTP LibMarkdown LibShell)
|
||||
|
|
|
@ -38,29 +38,24 @@ ErrorOr<HashMap<String, InstalledPort>> InstalledPort::read_ports_database()
|
|||
// FIXME: Skip over invalid entries instead?
|
||||
return Error::from_string_view("Database entry too short"sv);
|
||||
}
|
||||
auto string_type = parts[0];
|
||||
auto name = TRY(String::from_utf8(parts[1]));
|
||||
auto install_type_string = parts[0];
|
||||
auto port_name = TRY(String::from_utf8(parts[1]));
|
||||
|
||||
if (auto maybe_type = type_from_string(string_type); maybe_type.has_value()) {
|
||||
if (auto maybe_type = type_from_string(install_type_string); maybe_type.has_value()) {
|
||||
auto const type = maybe_type.release_value();
|
||||
if (parts.size() < 3)
|
||||
return Error::from_string_view("Port is missing a version specification"sv);
|
||||
auto version = TRY(String::from_utf8(parts[2]));
|
||||
|
||||
auto& port = ports.ensure(name, [&] { return InstalledPort { name, {}, {} }; });
|
||||
port.m_type = type;
|
||||
port.m_version = move(version);
|
||||
} else if (string_type == "dependency"sv) {
|
||||
// Accept an empty dependency list.
|
||||
auto dependency_views = parts.span().slice(2);
|
||||
ports.ensure(port_name, [=] { return InstalledPort { port_name, MUST(String::from_utf8(parts[2])), type }; });
|
||||
} else if (install_type_string == "dependency"sv) {
|
||||
Vector<String> dependencies;
|
||||
TRY(dependencies.try_ensure_capacity(dependency_views.size()));
|
||||
for (auto const& view : dependency_views)
|
||||
dependencies.unchecked_append(TRY(String::from_utf8(view)));
|
||||
|
||||
TRY(dependencies.try_ensure_capacity(parts.size() - 2));
|
||||
for (auto const& dependency : parts.span().slice(2)) {
|
||||
dependencies.unchecked_append(TRY(String::from_utf8(dependency)));
|
||||
}
|
||||
// Assume the port as automatically installed if the "dependency" line occurs before the "manual"/"auto" line.
|
||||
// This is fine since these entries override the port type in any case.
|
||||
auto& port = ports.ensure(name, [&] { return InstalledPort { name, Type::Auto, {} }; });
|
||||
auto& port = ports.ensure(port_name, [&] { return InstalledPort { port_name, {}, Type::Auto }; });
|
||||
port.m_dependencies = move(dependencies);
|
||||
} else {
|
||||
return Error::from_string_literal("Unknown installed port type");
|
||||
|
|
|
@ -6,14 +6,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Port.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibSemVer/SemVer.h>
|
||||
|
||||
constexpr StringView ports_database = "/usr/Ports/installed.db"sv;
|
||||
|
||||
class InstalledPort {
|
||||
class InstalledPort : public Port {
|
||||
public:
|
||||
enum class Type {
|
||||
Auto,
|
||||
|
@ -24,14 +27,16 @@ public:
|
|||
static ErrorOr<HashMap<String, InstalledPort>> read_ports_database();
|
||||
static ErrorOr<void> for_each_by_type(HashMap<String, InstalledPort>&, Type type, Function<ErrorOr<void>(InstalledPort const&)> callback);
|
||||
|
||||
InstalledPort(String name, Type type, String version)
|
||||
: m_name(move(name))
|
||||
InstalledPort(String const& name, String const& version, Type type)
|
||||
: Port(name, version)
|
||||
, m_type(type)
|
||||
, m_version(move(version))
|
||||
{
|
||||
}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
Type type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
StringView type_as_string_view() const
|
||||
{
|
||||
if (m_type == Type::Auto)
|
||||
|
@ -41,13 +46,9 @@ public:
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView name() const { return m_name.bytes_as_string_view(); }
|
||||
StringView version() const { return m_version.bytes_as_string_view(); }
|
||||
ReadonlySpan<String> dependencies() const { return m_dependencies.span(); }
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
Type m_type;
|
||||
String m_version;
|
||||
Vector<String> m_dependencies;
|
||||
};
|
||||
|
|
70
Userland/Utilities/pkg/Port.h
Normal file
70
Userland/Utilities/pkg/Port.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Gurkirat Singh <tbhaxor@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibSemVer/SemVer.h>
|
||||
|
||||
constexpr auto normal_version_separators = ".-"sv;
|
||||
|
||||
class Port {
|
||||
|
||||
public:
|
||||
[[nodiscard]] String name() const { return m_name; }
|
||||
[[nodiscard]] String version_string() const { return m_version.has<String>() ? m_version.get<String>() : m_version.get<SemVer::SemVer>().to_string(); }
|
||||
[[nodiscard]] ErrorOr<SemVer::SemVer> version_semver() const
|
||||
{
|
||||
if (m_version.has<String>())
|
||||
return Error::from_string_view("This does not have semver"sv);
|
||||
return m_version.get<SemVer::SemVer>();
|
||||
}
|
||||
|
||||
Port(String const& name, String const& version)
|
||||
: m_name(name)
|
||||
, m_version(version)
|
||||
{
|
||||
set_version(version);
|
||||
}
|
||||
Port(String const& name, String const& version, char normal_version_separator)
|
||||
: m_name(name)
|
||||
, m_version(version)
|
||||
{
|
||||
set_version(version, normal_version_separator);
|
||||
}
|
||||
|
||||
void set_name(String const& name)
|
||||
{
|
||||
if (!name.is_empty())
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
void set_version(StringView const& version)
|
||||
{
|
||||
for (auto const& normal_version_separator : normal_version_separators) {
|
||||
auto semver_parsed = SemVer::from_string_view(version, normal_version_separator);
|
||||
if (!semver_parsed.is_error()) {
|
||||
m_version = semver_parsed.value();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_version = MUST(String::from_utf8(version));
|
||||
}
|
||||
|
||||
void set_version(StringView const& version, char normal_version_separator)
|
||||
{
|
||||
// If the user has provided the separator, it is safe to assume that they are certain about it.
|
||||
// Therefore, it is ideal to crash, indicating that their assumption is incorrect.
|
||||
m_version = MUST(SemVer::from_string_view(version, normal_version_separator));
|
||||
}
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
Variant<String, SemVer::SemVer> m_version;
|
||||
};
|
|
@ -11,9 +11,30 @@
|
|||
#include <LibMain/Main.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void print_port_details(InstalledPort const& port)
|
||||
static void print_port_details(InstalledPort const& port, Optional<AvailablePort> const& available_port)
|
||||
{
|
||||
outln("{}, installed as {}, version {}", port.name(), port.type_as_string_view(), port.version());
|
||||
out("{}, installed as {}, version {}", port.name(), port.type_as_string_view(), port.version_string());
|
||||
if (available_port.has_value()) {
|
||||
auto const& upstream_port = available_port.value();
|
||||
auto const& upstream_version = upstream_port.version_semver();
|
||||
auto const& this_version = port.version_semver();
|
||||
|
||||
if ((this_version.is_error() || upstream_version.is_error())) {
|
||||
if (upstream_port.version_string() != port.version_string()) {
|
||||
outln(" (upgrade available -> {})", upstream_port.version_string());
|
||||
}
|
||||
} else {
|
||||
auto const& ap_version = upstream_version.value();
|
||||
auto const& ip_version = this_version.value();
|
||||
if (ip_version.is_same(ap_version)) {
|
||||
outln(" (already on latest version)");
|
||||
} else if (ip_version.is_lesser_than(ap_version)) {
|
||||
outln(" (upgrade available {})", ap_version.to_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outln();
|
||||
}
|
||||
|
||||
if (!port.dependencies().is_empty()) {
|
||||
out(" Dependencies:");
|
||||
|
@ -51,16 +72,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
return 0;
|
||||
}
|
||||
|
||||
HashMap<String, InstalledPort> installed_ports;
|
||||
HashMap<String, AvailablePort> available_ports;
|
||||
if (show_all_installed_ports || !query_package.is_null()) {
|
||||
if (Core::System::access(ports_database, R_OK).is_error()) {
|
||||
warnln("pkg: {} isn't accessible, did you install a package in the past?", ports_database);
|
||||
return 1;
|
||||
}
|
||||
installed_ports = TRY(InstalledPort::read_ports_database());
|
||||
}
|
||||
|
||||
int return_value = 0;
|
||||
if (update_packages_db) {
|
||||
if (getuid() != 0) {
|
||||
|
@ -70,18 +81,27 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
return_value = TRY(AvailablePort::update_available_ports_list_file());
|
||||
}
|
||||
|
||||
if (!query_package.is_null()) {
|
||||
if (Core::System::access("/usr/Ports/AvailablePorts.md"sv, R_OK).is_error()) {
|
||||
outln("pkg: Please run this program with -u first!");
|
||||
return 0;
|
||||
}
|
||||
available_ports = TRY(AvailablePort::read_available_ports_list());
|
||||
if (Core::System::access(ports_database, R_OK).is_error()) {
|
||||
warnln("pkg: {} isn't accessible, did you install a package in the past?", ports_database);
|
||||
return 1;
|
||||
}
|
||||
HashMap<String, InstalledPort> installed_ports = TRY(InstalledPort::read_ports_database());
|
||||
|
||||
if (Core::System::access("/usr/Ports/AvailablePorts.md"sv, R_OK).is_error()) {
|
||||
outln("pkg: Please run this program with -u first!");
|
||||
return 0;
|
||||
}
|
||||
HashMap<String, AvailablePort> available_ports = TRY(AvailablePort::read_available_ports_list());
|
||||
|
||||
if (show_all_installed_ports) {
|
||||
outln("Manually-installed ports:");
|
||||
TRY(InstalledPort::for_each_by_type(installed_ports, InstalledPort::Type::Manual, [](auto& port) -> ErrorOr<void> {
|
||||
print_port_details(port);
|
||||
TRY(InstalledPort::for_each_by_type(installed_ports, InstalledPort::Type::Manual, [available_ports](InstalledPort const& port) -> ErrorOr<void> {
|
||||
auto available_port = available_ports.find(port.name());
|
||||
if (available_port != available_ports.end()) {
|
||||
print_port_details(port, available_port->value);
|
||||
} else {
|
||||
print_port_details(port, {});
|
||||
}
|
||||
return {};
|
||||
}));
|
||||
}
|
||||
|
@ -91,7 +111,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
outln("pkg: Queried package name is empty.");
|
||||
return 0;
|
||||
}
|
||||
return_value = TRY(AvailablePort::query_details_for_package(available_ports, installed_ports, query_package, verbose));
|
||||
AvailablePort::query_details_for_package(available_ports, installed_ports, query_package, verbose);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
|
|
Loading…
Add table
Reference in a new issue