Source code for meta_package_manager.managers.emerge

# Copyright Kevin Deldycke <kevin@deldycke.com> 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 typing import Iterator

from extra_platforms import UNIX_WITHOUT_MACOS

from meta_package_manager.base import Package, PackageManager
from meta_package_manager.capabilities import version_not_implemented


[docs] class Emerge(PackageManager): """The Gentoo package manager. Documentation: - https://wiki.gentoo.org/wiki/Portage#emerge - https://dev.gentoo.org/~zmedico/portage/doc/man/emerge.1.html See other command equivalences at: https://wiki.archlinux.org/title/Pacman/Rosetta """ homepage_url = "https://wiki.gentoo.org/wiki/Portage#emerge" platforms = UNIX_WITHOUT_MACOS requirement = "3.0.0" version_regex = r"Portage\s+(?P<version>\S+)" """ .. code-block:: shell-session ► emerge --version Portage 3.0.30 (python 3.9.9-final-0, gcc-11.2.1, 5.15.32-gentoo-r1 x86_64) """ @property def installed(self) -> Iterator[Package]: """Fetch installed packages. .. warning:: This suppose the ``qlist`` binary is available and present on the system. We do not search for it or try to resolves its canonical path with :py:attr:`PackageManager.cli_path <meta_package_manager.base.PackageManager.cli_path>`, as we do for the reference ``emerge`` binary. .. code-block:: shell-session ► qlist --installed --verbose --nocolor acct-group/audio-0-r1 acct-group/cron-0 app-admin/hddtemp-0.3_beta15-r29 app-admin/perl-cleaner-2.30 app-admin/system-config-printer-1.5.16-r1 app-arch/p7zip-16.02-r8 """ # Locate qlist. qlist_path = self.which("qlist") if not qlist_path: raise FileNotFoundError(qlist_path) output = self.run_cli( "--installed", "--verbose", "--nocolor", override_cli_path=qlist_path, ) regexp = re.compile( r""" ( ?P<package_id>\S+ # Non-whitespace string... (?!-r) # ...if not directly followred by the "-r" string. ) - # A dash. ( ?P<version>[^\s-]+ # Any non-whitespace/non-dash string. (?:-r\d+)? # Optional revision suffix led by a -, non-grouped. ) """, re.VERBOSE, ) for package in output.splitlines(): match = regexp.match(package) if match: package_id, installed_version = match.groups() yield self.package(id=package_id, installed_version=installed_version) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. .. code-block:: shell-session ► emerge --update --deep --pretend --columns --color n --nospinner @world [blocks B ] app-text/dos2unix [ebuild N ] app-games/qstat [25c] [ebuild R ] sys-apps/sed [2.4.7-r6] [ebuild U] net-fs/samba [2.2.8_pre1] [2.2.7a] [ebuild U] sys-devel/distcc [2.16] [2.13-r1] USE=ip6* -gtk [ebuild r U] dev-libs/icu [50.1.1:0/50.1.1] [50.1-r2:0/50.1] [ebuild r R ] dev-libs/libxml2 [2.9.0-r1:2] USE=icu """ output = self.run_cli( "--update", "--deep", "--pretend", "--columns", "--color", "n", "--nospinner", "@world", ) regexp = re.compile( r""" \[.+\] # Update state. \ # A space. (?P<package_id>\S+) # Non-whitespace string. \s+ # Any spacing. (?:\[ # Non-matching group # starting with a '['. (?P<latest_version>[^\s\/:]+) # Any non-spaced string # until a ':' or '/' is met. \S* # Left-over parts of the version, # after a ':' or '/'. \])? # Optional group ending with a ']'. \s+ # Any spacing. (?:\[ # Non-matching group # starting with a '['. (?P<installed_version>[^\s\/:]+) # Any non-spaced string # until a ':' or '/' is met. \S* # Left-over parts of the version, # after a ':' or '/'. \])? # Optional group ending with a ']'. """, re.VERBOSE, ) for package in output.splitlines(): match = regexp.match(package) if match: package_id, latest_version, installed_version = match.groups() yield self.package( id=package_id, latest_version=latest_version, installed_version=installed_version, )
[docs] def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. .. code-block:: shell-session ► emerge --search --color n --nospinner blah [ Results for search key : blah ] Searching... * sys-process/htop Latest version available: 1.0.2-r1 Latest version installed: [ Not Installed ] Size of files: 380 KiB Homepage: http://htop.sourceforge.net Description: interactive process viewer License: BSD GPL-2 * x11-drivers/nvidia-drivers Latest version available: 455.45.01-r1 Latest version installed: [ Not Installed ] Size of files: 180.214 KiB Homepage: https://www.nvidia.com/Download/Find.aspx Description: NVIDIA Accelerated Graphics Driver License: GPL-2 NVIDIA-r2 [ Applications found : 2 ] .. code-block:: shell-session ► emerge --search --color n --nospinner %^sed$ .. code-block:: shell-session ► emerge --searchdesc --color n --nospinner sed .. code-block:: shell-session ► emerge --searchdesc --color n --nospinner %^sed$ """ search_param = "--search" if extended: search_param = "--searchdesc" if exact: query = f"%^{query}$" output = self.run_cli(search_param, "--color", "n", "--nospinner", query) regexp = re.compile( r""" ^\*\s+(?P<package_id>\S+)\n \s+Latest\ version\ available:\s+(?P<latest_version>\S+)\n (?:\s+.+\n)+? \s+Description:\s+(?P<description>.+)\n """, re.MULTILINE | re.VERBOSE, ) for package_id, version, description in regexp.findall(output): yield self.package( id=package_id, description=description, latest_version=version, )
@version_not_implemented def install(self, package_id: str, version: str | None = None) -> str: """Install one package. .. code-block:: shell-session ► sudo emerge --color n --nospinner dev-vcs/git """ return self.run_cli("--color", "n", "--nospinner", package_id, sudo=True)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all packages (default) or only the one provided as parameter. .. code-block:: shell-session ► sudo emerge --update --newuse --deep --color n --nospinner @world """ return self.build_cli( "--update", "--newuse", "--deep", "--color", "n", "--nospinner", "@world", sudo=True, )
@version_not_implemented def upgrade_one_cli( self, package_id: str, version: str | None = None, ) -> tuple[str, ...]: """Generates the CLI to upgrade all packages (default) or only the one provided as parameter. .. code-block:: shell-session ► sudo emerge --update --color n --nospinner dev-vcs/git """ return self.build_cli( "--update", "--color", "n", "--nospinner", package_id, sudo=True, )
[docs] def sync(self) -> None: """Sync package metadata. .. code-block:: shell-session ► sudo emerge --sync --color n --nospinner """ self.run_cli("--sync", "--color", "n", "--nospinner", sudo=True)
[docs] def cleanup(self) -> None: """Removes things we don't need anymore. An update is forced before calling the clean commands, as `pointed to by the emerge documentation <https://wiki.gentoo.org/wiki/Gentoo_Cheat_Sheet#Recommended_method>`_: > As a safety measure, depclean will not remove any packages unless *all* > required dependencies have been resolved. As a consequence, it is often > necessary to run `emerge --update --newuse --deep @world` prior to depclean. .. warning:: This suppose the ``eclean`` binary is available and present on the system. We do not search for it or try to resolves its canonical path with :py:attr:`PackageManager.cli_path <meta_package_manager.base.PackageManager.cli_path>`, as we do for the reference ``emerge`` binary. .. code-block:: shell-session ► sudo emerge --update --newuse --deep --color n --nospinner @world ► sudo emerge --depclean ► sudo eclean distfiles """ # Forces an upgrade first, as recommended by emerge documentation. self.upgrade() self.run_cli("--depclean", sudo=True) eclean_path = self.which("eclean") if eclean_path: self.run_cli("distfiles", override_cli_path=eclean_path, sudo=True)