Skip to content

Backend Internals

Lokesh Kumar edited this page Aug 7, 2026 · 3 revisions

Backend Internals

This document is intended for developers and contributors looking to understand how pkgwrap abstracts package managers and how to implement new ones.

Architecture Flow

When a user runs a command like pkgwrap install vim, the execution follows a specific layered flow:

  1. CLI Layer (cli.py): Parses the user's arguments, detects if the user is already running as root (via os.geteuid()), and captures flags like -y (auto-yes).
  2. Detection Layer (detector.py): Identifies the host operating system's primary package manager and returns its string identifier (e.g., "apt" or "pacman").
  3. Registry & Instantiation (backends/__init__.py): Maps the string identifier to the corresponding Backend subclass and instantiates it.
  4. Backend Implementation (base.py & Subclasses): The specific subclass formats the exact shell command needed (e.g., ["apt", "install", "-y", "vim"]) and passes it back up to the base class's _run_command() method.
  5. Execution (base.py): The _run_command() method handles any necessary privilege escalation (sudo), user confirmations, and error handling before safely invoking the native command via subprocess.run().

The Backend Base Class

All package managers must implement the Backend abstract base class located in src/pkgwrap/backends/base.py.

Required Methods

Subclasses must define their name property and implement the following abstract methods:

  • install(self, package: str, already_root: bool = False, auto_yes: bool = False)
  • remove(self, package: str, already_root: bool = False, auto_yes: bool = False)
  • update(self, already_root: bool = False, auto_yes: bool = False)
  • search(self, query: str, already_root: bool = False, auto_yes: bool = False)

Each method is responsible for constructing the native command as a list of strings and passing it to self._run_command(). They must return a subprocess.CompletedProcess object.

Execution Parameters

When a subclass calls self._run_command(), or when cli.py calls a backend method, three critical boolean parameters control the execution context:

  • require_sudo: Hardcoded by the backend implementation. Dictates whether the native package manager requires root privileges for a specific operation. For example, AptBackend.install() sets this to True, but BrewBackend.install() sets it to False.
  • already_root: Passed down from cli.py. If True, the user executed pkgwrap as root (e.g., sudo pkgwrap install). This tells the base class to skip prepending sudo and to skip confirmation prompts.
  • auto_yes: Passed down from cli.py if the user provided the -y flag. This tells the base class to skip the "Do you want to proceed with sudo?" prompt, and it is usually passed directly into the native command (e.g., adding -y to the apt command).

Detection Logic (detector.py)

The detect_backend() function is the brain of the wrapper. To ensure speed, it first checks a local cache (a temporary file storing the last detected backend). If there is a cache miss, it runs a detection sequence.

Special Casing: Termux and FreeBSD

Because both Termux (Android) and FreeBSD use a package manager named pkg, we cannot rely purely on shutil.which("pkg"). Furthermore, Termux includes an apt wrapper by default.

  1. Termux (Highest Priority): The detector explicitly checks os.environ for PREFIX containing com.termux or the presence of TERMUX_VERSION. If found, it immediately returns the pkg backend for Termux, ignoring any apt binaries on the system.
  2. FreeBSD: The detector checks if platform.system().lower() == "freebsd". If true and pkg is available, it routes to the freebsd_backend.

Priority Fallback

If neither Termux nor FreeBSD is detected, detector.py iterates through a prioritized list of generic package managers using shutil.which(). The priority order is carefully chosen to favor standard system managers over secondary ones:

  1. apt (Debian/Ubuntu)
  2. pacman (Arch)
  3. dnf (Fedora/RHEL)
  4. apk (Alpine)
  5. zypper (openSUSE)
  6. xbps-install (Void Linux)
  7. nix-env (NixOS)
  8. eopkg (Solus)
  9. brew (macOS / Linuxbrew)

The first available binary triggers a cache write and returns the corresponding backend identifier.

Clone this wiki locally