Source code for meta_package_manager.managers.guix

# 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 extra_platforms import LINUX_LIKE

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

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

    from ..base import Package


[docs] class Guix(PackageManager): """GNU Guix functional package manager. .. note:: All operations target the current user's default profile. Declarative system configuration (Guix System ``config.scm``) is not covered. """ homepage_url = "https://guix.gnu.org" platforms = LINUX_LIKE requirement = ">=1.0.0" version_regexes = (r"guix \(GNU Guix\) (?P<version>\S+)",) """ .. code-block:: shell-session $ guix --version guix (GNU Guix) 1.4.0 """ _SEARCH_FIELD_REGEXP = re.compile( r"^(?P<field>\w[\w-]*):\s+(?P<value>.+)$", ) """Match a single recutils field line (``name: value``).""" _OUTDATED_REGEXP = re.compile( r"^\s+(?P<package_id>\S+)\s+(?P<installed_version>\S+)\s+β†’\s+(?P<latest_version>\S+)\s*$", re.MULTILINE, ) """Match an upgrade line from ``guix upgrade --dry-run``. Sample output:: The following packages would be upgraded: hello 2.12.1 β†’ 2.12.3 sed 4.8 β†’ 4.9 """ @property def installed(self) -> Iterator[Package]: """Fetch installed packages. Output is tab-separated: name, version, output, store path. .. code-block:: shell-session $ guix package --list-installed hello 2.10 out /gnu/store/...-hello-2.10 python 3.10.7 out /gnu/store/...-python-3.10.7 """ output = self.run_cli("package", "--list-installed") for line in output.splitlines(): parts = line.split("\t") if len(parts) >= 2: yield self.package( id=parts[0], installed_version=parts[1], ) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. Relies on ``guix upgrade --dry-run`` which lists every package that would be upgraded without modifying the user profile. .. code-block:: shell-session $ guix upgrade --dry-run The following packages would be upgraded: hello 2.12.1 β†’ 2.12.3 sed 4.8 β†’ 4.9 """ output = self.run_cli("upgrade", "--dry-run") 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. .. caution:: Search does not support extended or exact matching. So we return the best subset of results and let :py:meth:`meta_package_manager.base.PackageManager.refiltered_search` refine them. Results are printed in recutils format with records separated by blank lines. .. code-block:: shell-session $ guix search hello name: hello version: 2.10 outputs: out systems: x86_64-linux i686-linux dependencies: glibc@2.35 ... location: gnu/packages/base.scm:86:2 homepage: https://www.gnu.org/software/hello/ license: GPL 3+ synopsis: Hello, GNU world: an example GNU package description: GNU Hello prints the message "Hello, world!" + and then exits. It serves as an example of standard + GNU coding practices. relevance: 10 """ output = self.run_cli("search", query) for record in re.split(r"\n\n+", output.strip()): fields: dict[str, str] = {} for line in record.splitlines(): match = self._SEARCH_FIELD_REGEXP.match(line) if match: fields[match.group("field")] = match.group("value") # Continuation lines (``+ ...``) are ignored; we only need the # first line of multi-line fields like description. name = fields.get("name") if name: yield self.package( id=name, description=fields.get("synopsis"), latest_version=fields.get("version"), )
[docs] @version_not_implemented def install(self, package_id: str, version: str | None = None) -> str: """Install one package. .. code-block:: shell-session $ guix install hello """ return self.run_cli("install", package_id)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all packages. .. code-block:: shell-session $ guix upgrade """ return self.build_cli("upgrade")
[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 $ guix upgrade hello """ return self.build_cli("upgrade", package_id)
[docs] def remove(self, package_id: str) -> str: """Remove one package. .. code-block:: shell-session $ guix remove hello """ return self.run_cli("remove", package_id)
[docs] def sync(self) -> None: """Fetch the latest Guix channel revisions. .. code-block:: shell-session $ guix pull """ self.run_cli("pull")
[docs] def cleanup(self) -> None: """Collect garbage in the store. .. code-block:: shell-session $ guix gc """ self.run_cli("gc")