# 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, 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 Nix(PackageManager):
"""Nix, the functional package manager.
.. note::
All operations use the imperative ``nix-env`` interface, which manages
a per-user package profile. Declarative approaches (NixOS modules,
home-manager) are not covered.
Channel refresh (``sync``) and store garbage collection (``cleanup``) shell
out to the sibling ``nix-channel`` and ``nix-collect-garbage`` binaries
installed alongside ``nix-env``, not to ``nix-env`` itself.
"""
homepage_url = "https://nixos.org"
platforms = LINUX_LIKE, MACOS
requirement = ">=2.0.0"
cli_names = ("nix-env",)
version_regexes = (r"nix-env \(Nix\) (?P<version>\S+)",)
"""
.. code-block:: shell-session
$ nix-env --version
nix-env (Nix) 2.18.1
"""
_OUTDATED_REGEXP = re.compile(
r"^(?P<name_version>\S+)\s+<\s+(?P<latest_version>\S+)",
re.MULTILINE,
)
@property
def installed(self) -> Iterator[Package]:
"""Fetch installed packages.
.. code-block:: shell-session
$ nix-env --query --installed
hello-2.12.1
nix-2.18.1
python3-3.11.6
"""
output = self.run_cli("--query", "--installed")
for line in output.splitlines():
if split := self.split_name_version(line.strip()):
package_id, version = split
yield self.package(id=package_id, installed_version=version)
@property
def outdated(self) -> Iterator[Package]:
"""Fetch outdated packages.
.. code-block:: shell-session
$ nix-env --query --upgradeable --compare-versions
hello-2.12.1 < 2.13.0
python3-3.11.6 < 3.12.0
"""
output = self.run_cli(
"--query",
"--upgradeable",
"--compare-versions",
)
for match in self._OUTDATED_REGEXP.finditer(output):
if split := self.split_name_version(match.group("name_version")):
package_id, version = split
yield self.package(
id=package_id,
installed_version=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.manager.PackageManager.refiltered_search`
refine them.
.. caution::
``nix-env --query --available`` evaluates the full nixpkgs set and
can be slow on first invocation.
.. code-block:: shell-session
$ nix-env --query --available --attr-path --description 'hello'
nixpkgs.hello hello-2.12.1 A program that produces a friendly greeting
"""
output = self.run_cli(
"--query",
"--available",
"--attr-path",
"--description",
query,
)
for line in output.splitlines():
# Columns are padded with variable whitespace (2+ spaces).
parts = re.split(r"\s{2,}", line.strip(), maxsplit=2)
if len(parts) < 2:
continue
name_version = parts[1]
description = parts[2] if len(parts) > 2 else None
if split := self.split_name_version(name_version):
package_id, version = split
yield self.package(
id=package_id,
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
$ nix-env --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
$ nix-env --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
$ nix-env --upgrade hello
"""
return self.build_cli("--upgrade", package_id)
[docs]
def remove(self, package_id: str) -> str:
"""Remove one package.
.. code-block:: shell-session
$ nix-env --uninstall hello
"""
return self.run_cli("--uninstall", package_id)
[docs]
def sync(self) -> None:
"""Update Nix channel metadata.
.. code-block:: shell-session
$ nix-channel --update
"""
self.run_cli(
"--update",
override_cli_path=self.sibling_cli("nix-channel", same_dir=True),
)
[docs]
def cleanup(self) -> None:
"""Remove old generations and garbage-collect the Nix store.
.. code-block:: shell-session
$ nix-collect-garbage --delete-old
"""
self.run_cli(
"--delete-old",
override_cli_path=self.sibling_cli("nix-collect-garbage", same_dir=True),
)