Source code for meta_package_manager.managers.flatpak

# 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 UNIX_WITHOUT_MACOS

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 Flatpak(PackageManager): """Flatpak manages sandboxed desktop applications pulled from remotes like Flathub. mpm covers applications only: every listing passes `--app`, so runtimes and SDKs stay out of scope. Listings are requested with `--columns=name,application,version --ostree-verbose` and parsed as tab-separated rows. ```{note} All operations target the system-wide scope except `cleanup` which only repairs the user installation. Per-scope targeting (system vs user) is tracked in [#1725](https://github.com/kdeldycke/meta-package-manager/issues/1725). ``` ```{note} Escalation is polkit's job, so no operation is marked `sudo`: flatpak hands system-scope mutations to its privileged system helper over D-Bus, which authorizes them through polkit (Flathub documents plain `flatpak install`). Under a strict polkit policy, unattended mutations need a rule permitting them without interactive authentication. ``` ```{caution} `outdated` reads each pending update's latest version from `remote-ls --updates`, then runs one `flatpak info` per package to recover its installed version: a follow-up CLI call for every outdated app. ``` ```{note} Brewfile backups emit the bare `flatpak "id"` form: mpm does not capture each app's origin remote, so `brew bundle install` restores through Flathub and non-Flathub apps must be edited in by hand. ``` """ homepage_url = "https://flatpak.org" logo = "flatpak" brewfile_entry_type = "flatpak" """Mapped to Homebrew Bundle's `flatpak` extension. The Brewfile `flatpak` entry supports a `with: ["remote"]` keyword for non-default remotes. mpm's {meth}`installed` does not currently capture the origin remote per package, so the dump emits the bare `flatpak "id"` form; `brew bundle install` then resolves through the default `flathub` remote. Non-flathub flatpaks need to be edited in by hand after the dump. """ platforms = UNIX_WITHOUT_MACOS requirement = ">=1.2.0" _LIST_REGEXP = re.compile( r"(?P<name>.+?)\t(?P<package_id>\S+)\t?(?P<latest_version>.*)", ) _SEARCH_REGEXP = re.compile( r""" ^(?P<package_name>\S+)\t (?P<description>.+)\t (?P<package_id>\S+)\t (?P<version>\S+)\t (?P<branch>\S+)\t (?P<remotes>.+) """, re.VERBOSE, ) version_regexes = (r"Flatpak\s+(?P<version>\S+)",) """ ```{code-block} shell-session $ flatpak --version Flatpak 1.4.2 ``` """ @property def installed(self) -> Iterator[Package]: """Fetch installed packages. ```{code-block} shell-session $ flatpak list --app --columns=name,application,version \ > --ostree-verbose Peek com.uploadedlobster.peek 1.3.1 Fragments de.haeckerfelix.Fragments 1.4 GNOME MPV io.github.GnomeMpv 0.16 Syncthing GTK me.kozec.syncthingtk v0.9.4.3 Builder org.flatpak.Builder ``` """ output = self.run_cli( "list", "--app", "--columns=name,application,version", "--ostree-verbose", ) for package in output.splitlines(): match = self._LIST_REGEXP.match(package) if match: name, package_id, installed_version = match.groups() yield self.package( id=package_id, name=name, installed_version=installed_version, ) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. ```{code-block} shell-session $ flatpak remote-ls --app --updates --columns=name,application,version \ --ostree-verbose GNOME Dictionary org.gnome.Dictionary 3.26.0 Files org.gnome.Nautilus 42.2 ``` """ output = self.run_cli( "remote-ls", "--app", "--updates", "--columns=name,application,version", "--ostree-verbose", ) for package in output.splitlines(): match = self._LIST_REGEXP.match(package) if match: name, package_id, latest_version = match.groups() info_installed_output = self.run_cli( "info", "--ostree-verbose", package_id, ) current_version = re.search( r"version:\s(?P<version>\S.*?)\n", info_installed_output, re.IGNORECASE, ) installed_version = ( current_version.group("version") if current_version else None ) yield self.package( id=package_id, name=name, latest_version=latest_version, installed_version=installed_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 {meth}`meta_package_manager.manager.PackageManager.refiltered_search` refine them. ``` ```{code-block} shell-session $ flatpak search gitg --ostree-verbose gitg GUI for git org.gnome.gitg 3.32.1 stable flathub ``` """ output = self.run_cli("search", query, "--ostree-verbose") for ( package_name, description, package_id, version, _branch, _remotes, ) in self._SEARCH_REGEXP.findall(output): yield self.package( id=package_id, name=package_name, description=description, latest_version=version, )
[docs] @version_not_implemented def install(self, package_id: str, version: str | None = None) -> str: """Install one package. ```{code-block} shell-session $ flatpak install --noninteractive org.gnome.Dictionary ``` """ return self.run_cli("install", "--noninteractive", package_id)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all outdated packages. ```{code-block} shell-session $ flatpak update --noninteractive ``` """ return self.build_cli("update", "--noninteractive")
[docs] @version_not_implemented def upgrade_one_cli( self, package_id: str, version: str | None = None, ) -> tuple[str, ...]: """Generates the CLI to upgrade the provided package. ```{code-block} shell-session $ flatpak update --noninteractive org.gnome.Dictionary ``` """ return self.build_cli("update", "--noninteractive", package_id)
[docs] def remove(self, package_id: str) -> str: """Remove one package. ```{code-block} shell-session $ flatpak uninstall --noninteractive org.gnome.Dictionary ``` """ return self.run_cli("uninstall", "--noninteractive", package_id)
[docs] def cleanup_orphan(self) -> None: """Uninstall runtimes and extensions no longer used by any installed app. ```{code-block} shell-session $ flatpak uninstall --unused --noninteractive ``` """ self.run_cli("uninstall", "--unused", "--noninteractive")
[docs] def cleanup_repair(self) -> None: """Verify and repair the per-user installation. See the [`flatpak repair` reference](https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-repair). ```{code-block} shell-session $ flatpak repair --user ``` """ self.run_cli("repair", "--user")
[docs] def doctor_cli(self) -> tuple[str, ...]: """Generates the CLI running the native self-diagnosis. The read-only twin of {meth}`cleanup_repair`: `--dry-run` reports what a repair of the per-user installation would fix without touching it. ```{code-block} shell-session $ flatpak repair --user --dry-run ``` """ return self.build_cli("repair", "--user", "--dry-run")