Source code for meta_package_manager.managers.zero_install

# 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 typing import ClassVar

from extra_platforms import ALL_PLATFORMS

from ..capabilities import search_capabilities
from ..manager import PackageManager

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

    from ..package import Package


[docs] class ZeroInstall(PackageManager): """Zero Install is a decentralised, cross-platform installation system. There is no central registry. A program is identified by the URL of its feed, an XML document its own publisher hosts and signs with their own GPG key, so a package id here is a feed URI rather than a short name. Short names collide freely across publishers: one search for `python` returns a `Python` feed from `apps.0install.net`, another from `0install.de` and a third from `dispcalgui.hoech.net`. A second identifier sits beside the URI. An *application* is a pet name the user invents and binds to a feed, which is what `destroy` and `update` address. The two identifier spaces are what shape the operation set below. ```{caution} No `installed`: `0install list` looks like an inventory and is not. It prints the URI of every feed in the local cache, which is the set of feeds ever fetched rather than the set of programs installed. Upstream says so in the command's own source, `src/cli/list_ifaces.ml`, which calls `Feed_cache.list_all_feeds` under the comment *"Actually, we list all the cached feeds. Close enough."* Driving it proves how wide the gap is. On a host where exactly one application was added, `0install list` returned thirteen URIs: the feeds the solver merely consulted to resolve dependencies, and `xz`, whose selection had failed outright with *"No usable implementations"* and which downloaded not one byte. Reporting those as installed would be false. The listing also never prints a pet name, so nothing it emits can be handed back to `destroy`. ``` ```{caution} No `install`: `0install add` takes *two* mandatory arguments, the pet name to create and the feed URI to bind it to, and refuses a lone URI. mpm's install carries a single package id, which cannot supply both, and inventing a pet name for the user would name their application for them. `sheldon` declines the operation for the same reason. ``` ```{note} No `outdated`: nothing reports staleness without acting on it. `update` does print when a newer version exists, but it is the upgrade itself and needs an application named on the command line. ``` ```{note} No `upgrade` of either shape. `0install update` requires an application or a URI and the tool ships no bulk form, so there is no `upgrade --all` to build. The single-package form is out for a second reason: mpm resolves which manager sources a package by querying its inventory, and this manager has none to query, so the operation could never be dispatched here. ``` ```{note} No `sync`: refreshing is the `--refresh` flag of the commands that already resolve a feed, never a command of its own. ``` ```{note} No version floor is declared. Every subcommand this class drives predates the oldest release upstream's `CHANGES.md` documents, so no floor could be verified rather than guessed. The class was driven against `2.18`. ``` Documentation: [0install](https://0install.net). """ name = "Zero Install" homepage_url = "https://0install.net" platforms = ALL_PLATFORMS """The OCaml implementation builds for Windows alongside every Unix, and ships the manifest and resource files to prove it under `src/windows`. """ cli_names = ("0install",) """The binary leads with a digit, where the manager id cannot. Every id doubles as a `--<id>` selector, and Click derives an option's Python identifier from that flag, which leaves `--0install` named by the empty string. So the id follows the project's own name, Zero Install, and only the binary keeps its digit. """ pre_args = ("--console",) """Keeps every call on the terminal. 0install switches to a graphical policy editor whenever it needs the network and `DISPLAY` is set, so a search run from a desktop session would otherwise open a window and wait for a human. """ version_regexes = (r"0install\s+\(zero-install\)\s+(?P<version>\S+)",) """ ```{code-block} shell-session $ 0install --console --version 0install (zero-install) 2.18 Copyright (C) 2019 Thomas Leonard This program comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law. You may redistribute copies of this program under the terms of the GNU Lesser General Public License. For more information about these matters, see the file named COPYING. Compiled with D-Bus support: false HTTP client library: libcurl (C) ``` """ _SEARCH_REGEXP: ClassVar = re.compile( r"^(?P<package_id>\S+)\n {2}(?P<name>\S+) - (?P<description>.*) \[\d+%\]$", re.MULTILINE, ) """Matches one two-line search record. A record is the feed URI on its own line, then an indented line carrying the short name, a description and the mirror's relevance score. Two lines per record is what rules out a bundled definition for this manager. The URI is the package id, being the only unique one: short names collide across publishers, and the same `Python` name is served by three separate feeds. The short name is kept as the package name, which is the half a user recognises and searches on. """
[docs] @search_capabilities(extended_support=False, exact_support=False) def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. The query goes to the mirror server, which indexes feeds it has been told about. Results carry no version, the mirror indexing feeds rather than the implementations inside them. ```{caution} Search does not support extended or exact matching. ``` ```{code-block} shell-session $ 0install --console search hello http://gfxmonk.net/dist/0install/template.xml template - A command-line jinja2 template script [100%] http://gfxmonk.net/dist/0install/python-pyquery.xml python-pyquery - A jquery-like library for python [45%] http://gfxmonk.net/dist/0install/python-argh.xml python-argh - An unobtrusive argparse wrapper with natural syntax [38%] http://gfxmonk.net/dist/0install/should.js.xml should.js - should.js [8%] http://gfxmonk.net/dist/0install/python-colorama.xml python-colorama - Cross-platform colored terminal text. [7%] ``` """ output = self.run_cli("search", query) for match in self._SEARCH_REGEXP.finditer(output): yield self.package( id=match.group("package_id"), name=match.group("name"), description=match.group("description"), )
[docs] def remove(self, package_id: str) -> str: """Remove one package. `destroy` takes the pet name alone, the one `add` created, and deletes the application directory with any launcher script it placed on the user's path. A feed URI is refused with `No such application`, so the URIs that `search` yields are not what this operation consumes. ```{code-block} shell-session $ 0install --console destroy zeroinstall ``` """ return self.run_cli("destroy", package_id)
[docs] def cleanup_cache(self) -> None: """Optimise the implementation cache. Hard-links files shared by several implementations in the store, so a program kept at more than one version stops paying for each copy. ```{code-block} shell-session $ 0install --console store optimise Optimising /Users/kde/.cache/0install.net/implementations [0 / 1] Reading manifests...Original size : 2.1 MB (excluding the 27.9 KB of manifests) Already saved : 0 bytes Optimised size : 2.1 MB Space freed up : 40 bytes (0.00%) Optimisation complete. ``` """ self.run_cli("store", "optimise")