Source code for meta_package_manager.managers.prt_get

# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import re

from extra_platforms import CRUX

from ..capabilities import search_capabilities, version_not_implemented
from ..manager import PackageManager

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Iterator

    from ..package import Package


[docs] class PrtGet(PackageManager): """The ports front-end of CRUX. Documentation: [`prt-get(8)` man page](https://crux.nu/Main/Prt-get). ```{caution} CRUX builds every port from source, so an install or an upgrade compiles. `mpm` caps a mutating operation at 500 seconds, which a large port outlasts by hours. Raise it for this manager with `[mpm.overrides.prt-get] timeout`. ``` ```{note} {meth}`sync` runs the sibling `ports` binary rather than `prt-get`, that being the tool CRUX gives the ports tree. `mpm` resolves it from the same directory as {attr}`cli_path <meta_package_manager.execution.CLIExecutor.cli_path>`. ``` ```{warning} `prt-get listorphans` is deliberately left unmapped. It lists "ports with no packages depending on them", which on a stock CRUX install includes `bash`, `binutils` and `coreutils`: the base system is depended on by nothing, so feeding that list to an orphan sweep would remove the machine. That is a different question from the one {meth}`~PackageManager.orphans` asks, which is which packages were pulled in as dependencies and are no longer required. ``` """ id = "prt-get" """The CLI name, which the `PrtGet` class name cannot spell: a hyphen is not valid in a Python identifier. """ name = "CRUX prt-get" homepage_url = "https://crux.nu/Main/Prt-get" keywords = ("crux",) platforms = CRUX default_sudo = True requirement = ">=5.19" """The `5.19` series is what every supported CRUX ships: `5.19.6` on CRUX `3.7` and `5.19.9` on CRUX `3.8`. This is not a bisected minimum. Every operation below is far older than that: upstream's own `ChangeLog` dates `printf` to `0.3.4`, `listinst` to `0.3.1pre1`, `dsearch` to `0.2.9` and `sysup` to `0.4.0alpha2`, all of them predating the newest entry that file carries (`5.16`, 2008). So the floor records the series this wrapper was driven against rather than claiming compatibility with releases nobody runs. """ cli_names = ("prt-get",) version_cli_options = ("version",) """`prt-get` answers `--version` with `prt-get: Unknown option: --version`. The version is a subcommand instead. """ version_regexes = (r"prt-get\s+(?P<version>\S+)",) """ ```{code-block} shell-session $ prt-get version prt-get 5.19.5 by Johannes Winkelmann, [email protected] ``` """ _INSTALLED_REGEXP = re.compile( r"^(?P<package_id>\S+)\s+(?P<installed_version>\S+)\s*$", re.MULTILINE, ) """Match the `name version-release` rows of `prt-get listinst -v`.""" _OUTDATED_REGEXP = re.compile( r"^(?P<package_id>\S+)\s+" r"(?P<installed_version>\S+)\s+" r"(?P<latest_version>\S+)\s*$", re.MULTILINE, ) """Match the rows of the `prt-get diff` table, skipping its two-line header. Neither header line survives the anchors: `Port Installed Available in the ports tree` carries four more tokens past the third group, and the `Differences between installed packages and ports tree:` title carries five, so `\\s*$` fails on both. Data rows are padded to fixed-width columns, hence the trailing `\\s*`. """ _SEARCH_REGEXP = re.compile( r"^(?P<package_id>[^|]+)\|" r"(?P<latest_version>[^|]*)\|" r"(?P<description>.*)$", re.MULTILINE, ) """Match the `name|version|description` rows of the `printf` projection.""" @property def installed(self) -> Iterator[Package]: """Fetch installed packages. The `[Config error: can't access /usr/ports/<collection>]` lines a host with an unsynced ports tree emits go to `stderr`, so they never reach this parser. ```{code-block} shell-session $ prt-get listinst -v acl 2.3.2-1 attr 2.5.2-1 autoconf 2.72-1 automake 1.17-1 bash 5.2.37-1 bc 1.08.1-1 ``` """ # `-v` has no long form: prt-get documents it as a bare general option. output = self.run_cli("listinst", "-v") for match in self._INSTALLED_REGEXP.finditer(output): yield self.package( id=match.group("package_id"), installed_version=match.group("installed_version"), ) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. ```{caution} Compares the installed packages against the local ports tree, so it reports what the last {meth}`sync` fetched and not what CRUX publishes right now. ``` ```{code-block} shell-session $ prt-get diff Differences between installed packages and ports tree: Port Installed Available in the ports tree acl 2.3.2-1 2.4.0-1 attr 2.5.2-1 2.6.0-1 autoconf 2.72-1 2.73-1 automake 1.17-1 1.18.1-1 bash 5.2.37-1 5.3.15-1 ``` """ output = self.run_cli("diff") for match in self._OUTDATED_REGEXP.finditer(output): yield self.package( id=match.group("package_id"), installed_version=match.group("installed_version"), latest_version=match.group("latest_version"), )
[docs] @search_capabilities(extended_support=False, exact_support=False) def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. Lists the whole ports tree and lets {meth}`meta_package_manager.manager.PackageManager.refiltered_search` narrow it. `prt-get` does own two search commands, but neither reports a version: `search` and `dsearch` both print bare port names, and only the `printf` projection carries the version and the description. The tree is local and small, so dumping it costs less than enriching each hit: 789 ports rendered in 34 ms on the host this was driven on. The format string holds no space, so the command `mpm` discloses at `--verbosity INFO` can be pasted back into a shell unquoted. ```{code-block} shell-session $ prt-get printf %n|%v|%d\\n a2ps|4.15.8|ASCII to Postscript converter (Prettyprint) abseil-cpp|20260817.0|Abseil Common Libraries (C++) acl|2.4.0|Access Control Lists library adwaita-icon-theme|50.0|Adwaita Icon Theme alsa-lib|1.2.16.1|ALSA libraries alsa-oss|1.1.8|ALSA OSS Emulation ``` """ output = self.run_cli("printf", r"%n|%v|%d\n") for match in self._SEARCH_REGEXP.finditer(output): yield self.package( id=match.group("package_id"), description=match.group("description").strip(), latest_version=match.group("latest_version"), )
[docs] @version_not_implemented def install(self, package_id: str, version: str | None = None) -> str: """Install one package, and the ports it depends on. `depinst` rather than `install`: the latter builds the named port alone and fails on the first missing dependency. ```{code-block} shell-session $ sudo prt-get depinst dosfstools =======> Building '/usr/ports/opt/dosfstools/dosfstools#4.2-1.pkg.tar.gz' succeeded. prt-get: installing dosfstools 4.2-1 -- Packages installed dosfstools prt-get: installed successfully ``` """ return self.run_cli("depinst", package_id, sudo=True)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all packages. ```{code-block} shell-session $ sudo prt-get sysup ``` """ return self.build_cli("sysup", sudo=True)
[docs] @version_not_implemented def upgrade_one_cli( self, package_id: str, version: str | None = None, ) -> tuple[str, ...]: """Generates the CLI to upgrade one package. ```{code-block} shell-session $ sudo prt-get update dosfstools ``` """ return self.build_cli("update", package_id, sudo=True)
[docs] def remove(self, package_id: str) -> str: """Remove one package. ```{code-block} shell-session $ sudo prt-get remove dosfstools -- Packages removed dosfstools ``` """ return self.run_cli("remove", package_id, sudo=True)
[docs] def sync(self) -> None: """Sync the ports tree. ```{code-block} shell-session $ sudo ports -u Updating file list from crux.nu::ports/crux-3.8/xorg/ Updating collection xorg Finished successfully ``` """ self.run_cli( "-u", override_cli_path=self.sibling_cli("ports", same_dir=True), sudo=True, )