meta_package_manager.execution module¶
CLI-execution engine shared by every package manager.
Runs one manager’s CLI in one subprocess: the
meta_package_manager.execution.CLIExecutor mixin (which
meta_package_manager.manager.PackageManager inherits) locates the binary
and runs it, the meta_package_manager.execution.CLIError exception carries
a failed call’s result, and meta_package_manager.execution.highlight_cli_name()
themes a binary’s name.
Scheduling many managers at once is the next altitude up, and lives in
meta_package_manager.dispatch: the concurrent fan-out primitives, the
lock families and the shared ✓/✘ trail. The sudo machinery that cuts
across both altitudes (credential priming, the keepalive, the hidden-prompt stall
watchdog) lives in meta_package_manager.sudo: this module only consumes
it, to wrap escalated commands and diagnose their failures.
Note
The name and intent mirror click_extra.execution from the sibling
click-extra project, where the
generic layers now live: the concurrency primitives (run_jobs/run_lanes
driven by mpm --jobs), the single-subprocess engine
(click_extra.execution.run_cli(), which disclosed invocations and
streams output to the logs), and the Ctrl+C machinery
(click_extra.execution.install_interrupt_handler() terminating the
in-flight children registered by run_cli). This module keeps what is
package-manager policy: per-operation timeouts, sudo escalation, cooldown
enforcement and dry-run.
- meta_package_manager.execution.DIAGNOSIS_HEAD_LINES: Final = 3¶
Leading lines of a failed command’s report relayed at
WARNING.Some CLIs open with their error and print a backtrace after it: RubyGems names the exception and its message first, then every frame. A tail alone then holds frames and no reason. The head stays short, since it is noise for the CLIs that conclude with their error.
- meta_package_manager.execution.DIAGNOSIS_TAIL_LINES: Final = 10¶
Trailing lines of a failed command’s report relayed at
WARNING.Most CLIs conclude with their actual error, so the tail is where the diagnosis lives; the cap keeps a verbose failure (a source build’s compiler spew) from flooding the default view. The raw streams are always available in full, live, at
DEBUG.
- meta_package_manager.execution.UNRUNNABLE_ERRNOS: Final[frozenset[int]] = frozenset({1, 2, 8, 13, 21})¶
OSErrornumbers meaning the CLI cannot be spawned, rather than that it failed.Each marks the manager unavailable and yields empty output, the same way
WinError193 does on Windows: something was found at that path, and it is not a program this host can run.ENOEXECis the POSIX twin of that Windows code, raised for a wrong-architecture binary, a truncated download, or a text file carrying the executable bit. GitHub’subuntu-26.04-armimage ships one, an x86pnpmat/usr/local/bin/pnpm, and the pool probes every manager it can see, so without this set that one binary aborts everympmrun on the host.Anything outside this set (a file-descriptor ceiling, an out-of-memory refusal) describes the machine rather than the CLI, and still propagates.
- meta_package_manager.execution.WIN_DEFAULT_PATHEXT: Final = '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC'¶
Executable suffixes to search on Windows when
%PATHEXT%is unset.A copy of CPython’s own
shutil._WIN_DEFAULT_PATHEXT, whichCLIExecutor.search_all_cli()reads to mirrorshutil.which()’s behaviour. That attribute is private, so it can be renamed or dropped by any release without notice, so reading it unguarded leaves every manager detection on Windows oneAttributeErroraway from failing. This constant is the fallback;test_windows_pathext_tracks_cpythoncompares the two on every platform and every interpreter the matrix covers, so a divergence surfaces as a named test failure rather than as a Windows-only crash.
- exception meta_package_manager.execution.CLIError(code, output, error, detail=None)[source]¶
Bases:
ExceptionAn error occurred when running package manager CLI.
The exception internally keeps the result of CLI execution.
- detail: str | None¶
The cause in a word or two, as the
✘trail line of the failed attempt shows it in parentheses.It stays
Nonefor a failure whose causempmdoes not know: the glyph already says the attempt failed. Seefailure_detail().
- property diagnosis: str¶
The command’s own account of its failure, capped for log relay.
Prefers
<stderr>, the conventional stream for error reporting, and falls back on<stdout>for the tools that report failures there (steamcmd); a command that died silently is reduced to its exit code. Only the firstDIAGNOSIS_HEAD_LINESand the lastDIAGNOSIS_TAIL_LINESlines are kept, around a counter of the truncated ones: an error either concludes a stream or opens it.
- meta_package_manager.execution.VERSION_PROBE: Final = 'version'¶
Pseudo-operation stamped on
_active_operationduring version detection.Not a member of
meta_package_manager.capabilities.Operations(no subcommand routes it), but it participates in the same per-operation machinery:OPERATION_TIMEOUTSbinds it to the short read-only cap, andCLIExecutor.run()demotes its command disclosure toDEBUGso the per-candidate probes cannot drown theINFOnarration.
- meta_package_manager.execution.format_plan_command(cmd_args, extra_env=None)[source]¶
Render a captured
mpm --plancommand as a copy-pasteable shell line.Unlike
click_extra.execution.format_cli_prompt()(styled, and prefixed with a$prompt sigil for logs and dry-runs), this returns a plain, unstyled, shell-quoted line: the forced environment assignments followed by the resolved binary and its arguments, ready to paste into a terminal or pipe into a shell. See the plan-mode branch ofCLIExecutor.run().- Return type:
- meta_package_manager.execution.PLAN_RECORDER: Final = <meta_package_manager.execution._PlanRecorder object>¶
Process-wide sink for
CLIExecutor.run()’s plan-mode captures.A module-level singleton because
runexecutes in the fan-out’s worker threads, where the click context is not reliably reachable. See_PlanRecorder.
- meta_package_manager.execution.highlight_cli_name(path, match_names)[source]¶
Highlight the binary name in the provided
path.The name is only highlighted when it matches one of the recognized
match_names, so an unrecognized binary stays plain. Matching is insensitive to case on Windows and case-sensitive on other platforms, thanks toos.path.normcase.The rendering is delegated to
click_extra.execution.highlight_bin_name(), the same helper behind the$-prompt and spawn-trace log lines, so thempm managerstable and the logs can never drift apart.
- meta_package_manager.execution.READ_ONLY_TIMEOUT: Final = 120¶
Default timeout (seconds) for read-only probes and queries.
These operations only inspect state, so a short cap lets a wedged binary fail fast instead of stalling the whole run. The value is generous enough for legitimately slow scans (a
guix searchfrom a development checkout, which evaluates every package definition) while still being far belowMUTATING_TIMEOUT.
- meta_package_manager.execution.MUTATING_TIMEOUT: Final = 500¶
Default timeout (seconds) for operations that change system state.
Installs, upgrades, removals, channel syncs and cleanups routinely build from source, download large archives or pull entire channels, so they need a long cap. Kept identical to the historical global default so these operations behave exactly as before when no explicit
--timeoutis given.
- meta_package_manager.execution.DEFAULT_TIMEOUT: Final = 500¶
Fallback timeout (seconds) for a CLI call whose operation is unknown.
Defaults to the conservative
MUTATING_TIMEOUT: when in doubt, wait rather than risk killing a legitimate long-running command.
- meta_package_manager.execution.OPERATION_TIMEOUTS: Final[dict[str, int]] = {'cleanup': 500, 'doctor': 500, 'install': 500, 'installed': 120, 'orphans': 120, 'outdated': 120, 'remove': 500, 'search': 120, 'sync': 500, 'upgrade': 500, 'upgrade_all': 500, 'version': 120}¶
Per-operation timeout defaults, applied only when the user has set no explicit
--timeout(or per-managertimeoutoverride).Keyed by the
meta_package_manager.capabilities.Operationsmember name, plus the special"version"detection probe. The keys are validated against theOperationsenum by the test suite so the two never drift apart. An operation absent from this map resolves toDEFAULT_TIMEOUT.
- meta_package_manager.execution.SPINNER_DELAY: Final = 0.1¶
Seconds a CLI call must run before its progress spinner appears.
Kept short so the spinner surfaces almost immediately on any call that is not instant: prompt feedback makes
mpmfeel responsive from the start rather than stalled during the first second. Only the quickest calls (cached version probes, trivial metadata queries) finish within this delay and stay silent; anything slower (aguix search, a source build) shows the spinner right away.
- meta_package_manager.execution.STILL_CALL_MARKER: Final = '·'¶
Marker opening the static line a call held still prints in place of its spinner.
A call that may raise a hidden
sudoprompt cannot animate (see_hidden_prompt_risk), so it prints its spinner’s label once instead, before the child starts. Deliberately not a spinner frame: a frozen⠋reads as a hung animation, where a neutral dot reads as a line that is not meant to move.
- meta_package_manager.execution.STILL_CALL_HINT: Final = 'may ask for your password'¶
Hint closing the static line of a call held still, faint like a timer.
It says why the line does not move and what may appear below it. It says “may”, since most such calls never escalate, and it never tells the user to type, for the reason the stall notice does not: the prompt may never come.
- meta_package_manager.execution.ITALIC_CAPABLE_TERMS: Final = ('xterm', 'tmux')¶
TERMprefixes whose terminfo declares the italic capability (sitm).An allow-list, because the terminals that lack italic outnumber the ones that have it:
infocmpreports nositmfor thescreen,linux,vt,ansi,rxvt,Eterm,cons25andsunentries, againstxtermandtmuxwhich declaresitm=\E[3m. Missing the capability is not benign: SGR3is then undefined rather than ignored, and some terminals render it as reverse video.The two prefixes cover what modern emulators actually export,
xterm-256colorbeing the default of iTerm2, Apple Terminal, VS Code and Alacritty, andxterm-kitty/xterm-ghosttythe exceptions that ship their own entry. A terminal outside them (wezterm, analacrittyentry where it is installed) loses the italic and keeps the plain rendering, which costs emphasis and never correctness. See_spinner_label().
- meta_package_manager.execution.operation_subject(manager_id, operation=None)[source]¶
Name what a manager is doing, as a
manager.operationattribute path.The subject both live indicators show: the per-call spinner of
CLIExecutor._make_spinnerand the✓/✘trail line ofcollect_from_managers(), so one manager reads the same whichever of the two is on screen. Shared rather than spelled twice, a drift between them being invisible until both are watched at once.Only the subject is shared. The trail keeps naming the manager alone in its command half’s place, a trail line covering a whole task where a spinner covers one invocation:
cleanupdispatches several commands per manager, and picking one of them to print would be arbitrary.
- meta_package_manager.execution.elapsed_clock(seconds)[source]¶
Render how long a finished call took the way a running spinner shows it.
The same
(2.3s)fragment the spinner’s timer draws, faint when styling is allowed, so a✓/✘trail line closes on the clock its spinner was counting.The elapsed time is the least important part of the line, and the faint attribute (SGR
2) says so without spending a color: every hue on this stream already carries a status, which is the same reason_spinner_label()leaves the command uncolored. Faint also beatsbright_blackfor the job, that being a color rather than an attribute, and one this project has already been bitten by (seeVERSION_PREFIX_COLOR).- Return type:
- class meta_package_manager.execution.CLIExecutor[source]¶
Bases:
objectLocate a manager’s CLI on the system and run it.
Mixin inherited by
meta_package_manager.manager.PackageManager. Owns the CLI-invocation configuration (names, search paths, environment, arguments, timeout) and the engine that searches for the binary, executes it, captures and normalizes its output, accumulates errors, and parses its self-reported version.Initialize
cli_errorslist.- cli_names: tuple[str, ...]¶
List of CLI names the package manager is known as.
This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.
- ..hint::
This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named
pythonorpython3.
By default, this property’s value is derived from the manager’s ID (see the
MetaPackageManager.__init__method above).
- cli_search_path: tuple[str, ...] = ()¶
List of additional path to help mpm hunt down the package manager CLI.
Must be a list of strings whose order dictates the search sequence.
Most of the time unnecessary:
meta_package_manager.execution.CLIExecutor.cli_pathworks well on all platforms.
- extra_env: ClassVar[Mapping[str, str | None] | None] = None¶
Additional environment variables to add to the current context.
Automatically applied on each
meta_package_manager.execution.CLIExecutor.run_cli()calls.
- pre_cmds: tuple[str, ...] = ()¶
Global list of pre-commands to add before before invoked CLI.
Automatically added to each
meta_package_manager.execution.CLIExecutor.run_cli()call.Used to prepend sudo or other system utilities.
- post_args: tuple[str, ...] = ()¶
Global list of options used before and after the invoked package manager CLI.
Automatically added to each
meta_package_manager.execution.CLIExecutor.run_cli()call.Essentially used to force silencing, low verbosity or no-color output.
- version_cli_options: tuple[str, ...] = ('--version',)¶
CLI options used to produce the version of the package manager.
The raw output produced by the package manager CLI will be parsed with the
version_regexesbelow to extract the version number.
- version_cli: str | None = None¶
Alternate binary probed for the manager’s version, instead of the main CLI.
Some manager suites expose no version flag on any of their own binaries (OpenBSD’s
pkg_add/pkg_info, Solaris’pkgadd/pkginfo): they ship with the base system and are versioned with the OS itself. Naming aversion_cli(likeuname) makes the version probe run that binary withversion_cli_optionsand parse its output withversion_regexes, while every operation keeps using the manager’s owncli_path. The binary is resolved withwhich(); the version resolves toNone(manager notfresh) when it is not found.
- version_regexes: tuple[str, ...] = ('(?P<version>\\S+)',)¶
Regular expressions used to extract the version number.
This property must be a tuple of strings, each of which is a valid regular expression that must contain a group named
<version>.The first of these regexes producing a match and returning non-empty
<version>group will be used as the version string of the package manager.That version string will then be sanitized and normalized by
meta_package_manager.execution.CLIExecutor.version.By default match the first part that is space-separated.
Caution
These regexes are compiled with
re.MULTILINEonly. They are not compiled withre.VERBOSE, so literal whitespace in the pattern is significant and matches whitespace in the CLI output.
- version_from_stderr: bool = False¶
Search
<stderr>too for the version, after<stdout>.Some tools print their version on
<stderr>and leave<stdout>empty: LURE reaches Go’s builtinprintln, and roswell prints its banner there. The probe reads the return value ofrun_cli(), which is<stdout>alone, so it would find nothing and leave such a manager permanently unavailable.Both streams are searched,
<stdout>first, rather than<stderr>alone: a version that moves to<stdout>in a later release must not take the manager offline. Off by default on purpose. A tool that prints nothing on<stdout>when broken often prints a warning on<stderr>, and the defaultversion_regexeswould read a version out of that warning.
- stderr_noise: Pattern[str] | None = None¶
Lines of
<stderr>that report no failure, like a tool’s routine warnings.The failure gate of
run()drops them before it reads<stderr>. A non-zero exit whose<stderr>holds nothing else then reads as a status, the way a silent<stderr>already does, and the diagnosis of a real failure keeps only the lines that explain it. The run snapshot keeps the whole stream.Nonekeeps every line.
- plan: bool = False¶
Capture state-changing CLI calls for inspection instead of running them.
Set by
mpm --plan. Unlikedry_run(which simulates every call, read-only queries included), plan mode lets the read-only queries (installed,outdated,search) run for real so the resolved plan reflects actual system state, and records only the state-changing commands (see_MUTATING_OPERATIONS) intoPLAN_RECORDER.
- timeout: int | None = None¶
Maximum number of seconds to wait for a CLI call to complete.
Nonemeans the user expressed no explicit preference: the effective cap is then resolved per-operation by_resolve_timeout()fromOPERATION_TIMEOUTS. A non-Nonevalue (the--timeoutflag or a per-manager override) wins for every operation.
- progress: bool = False¶
Whether CLI calls may show a progress spinner while they block.
Set by the CLI to an interactive, human-facing run only (a TTY, no serialized output, not at DEBUG verbosity). Even when
Truethe spinner still self-suppresses off a TTY: see_make_spinner(). Defaults toFalseso programmatic use stays silent.
- cooldown: timedelta | None = None¶
Minimum age a release must have before it can be installed or upgraded.
When set, the manager refuses to bring in any package version published more recently than
cooldownago. This is a mitigation against supply-chain attacks: a malicious release is typically detected and pulled within days of publication, so a waiting period keeps freshly-published (and potentially compromised) versions out of the system.Nonedisables the gate.Only managers able to enforce a release-age limit honor this, natively through
cooldown_env_varor through the per-package release-date probe ofrelease_date(); seesupports_cooldown.
- cooldown_policy: CooldownPolicy | None = None¶
Per-manager enforcement posture of an active release-age
cooldown.None(the default) inherits the policy resolved from--cooldownand the[mpm.cooldown]configuration, which the pool’s selection applies to every manager per run. Pin it explicitly through a[mpm.overrides.<id>]override, which keeps precedence over the global resolution:enforceholds the manager fail-closed even under a best-effort run,best-effortwaives the requirement and runs it without the safeguard, andoffexempts it from the gate entirely, suppressing the cutoff injection even where the manager could honor one.
- sudo: bool | None = None¶
User escalation policy: run this manager’s privileged commands with
sudo.None(the default) means the user expressed no preference, so the built-indefault_sudodecides.True/Falseforce escalation on or off for every operation this manager marks privileged (abuild_cli(..., sudo=True)call). Set globally bympm --sudo/mpm --no-sudoand per manager by the[mpm.overrides.<id>] sudoconfig key, the latter winning (see_select_managers).Only privileged operations are ever escalated, and only on a host carrying an escalator. Carrying one is the whole platform test, Windows included, since
gsudoand Microsoft’ssudo.exegive it one. A manager that escalates internally (internal_sudo) has no such markers and is never wrapped insudobympm: its ownsudoreuses the credential cache whenprime_sudo()finds it already warm, and is otherwise covered by the silent-call notice inrun().
- default_sudo: bool = False¶
Built-in escalation default, used when
sudoisNone.Falseon the base: most managers install into user-writable trees and never need root. The system package managers whose privileged operations require root (apt,dnf,pacman,zypper, …) set this toTrueso theirbuild_cli(..., sudo=True)operations escalate out of the box, while staying switchable off throughsudo(--no-sudoor config) for rootless setups.
- internal_sudo: bool = False¶
Marks a manager whose CLI invokes
sudoitself mid-run.Homebrew
caskruns it from installer artifacts,finkre-execs its root commands through it, and the AUR helpers callsudo pacmanfor their install steps. mpm never wraps such a manager’s commands: either none of its operations carry abuild_cli(..., sudo=True)marker (cask,fink), or itsdefault_sudo = Falsepolicy leaves the markers it inherits unescalated (the AUR helpers). Running the tool undersudois often forbidden outright (brewrefuses root,makepkgrefuses to build). Consumed byprime_sudo(), whose opportunistic probe keeps an already-warm credential cache alive for these internal escalations, and by the silent-call notice inrun(), which flags a possibly-hidden password prompt on a cold cache.Forcing
sudo = trueon such a manager (config key or--sudo) still never wraps its commands, but does promote it into the up-front prompt path ofprime_sudo(), unless the tool drops the cached credentials itself (resets_sudo_timestamp).
- internal_sudo_operations: ClassVar[frozenset[str]] = frozenset({'cleanup', 'install', 'remove', 'sync', 'upgrade', 'upgrade_all'})¶
Operations in which an internal escalator may run its own
sudo.Every mutating operation by default:
finkre-execsfink selfupdateandfink cleanupthroughsudolike its installs,shellyescalates in each mutating verb, and the AUR helpers callsudo pacmanto refresh their databases. (restorestamps"install", so it is covered.) A manager narrows the set when some of its commands never escalate, likecask.Matched against
_active_operationby_hidden_prompt_riskand_spends_warm_cache. On a terminal with a cold credential cache, a call in one of these operations runs last, without a spinner and under the stall watchdog, so the prompt of the tool’s ownsudois answerable. Detached like any other call, thatsudohas no terminal to ask on and gives up at once. On a warm cache, the call stays in the session of the terminal, where thatsudocan spend the cached credentials.mpmhandles a tool that drops them itself (resets_sudo_timestamp) as on a cold cache.Only read on a manager marked
internal_sudo.
- resets_sudo_timestamp: bool = False¶
Marks an internal escalator whose CLI drops the cached credentials itself.
Homebrew runs
sudo --reset-timestampon purpose, when a command first needs sudo, and at the start of every command before7.0.8. A warm cache therefore never spares the prompt of its ownsudo. So_hidden_prompt_riskholds for such a manager whatever the cache holds: a call that can prompt still runs last, without a spinner and under the stall watchdog. A forcedsudo = trueasks no password up front for it either:prime_sudo()warns that the policy does nothing.Only read on a manager marked
internal_sudo.
- privileged_operations: ClassVar[frozenset[str]] = frozenset({})¶
Operations with at least one command this manager marks privileged.
Keyed by the
Operationsmember name, likeoperation_notes. An operation belongs here when a method implementing it passessudo=Truetorun_cli()orbuild_cli(), with the method-to-operation map ofMETHOD_OPERATIONS. Those commands are the only ones the escalation policy (sudo, elsedefault_sudo) wraps in the escalator. A definition derives the set from the operations it markssudo = true.prime_sudo()reads it to prompt only for the managers that escalate a command of the current run. With[mpm.overrides.gem] sudo = true,mpm syncasks for no password: thegem sources --updatecommand ofsynccarries no marker.test_privileged_operationsholds the set to the markers in the method bodies.
- cooldown_env_var: ClassVar[str | None] = None¶
Environment variable this manager reads to honor a
cooldown.None(the default) means the manager has no native release-age mechanism to feed through the environment; it may still honor a cooldown through the per-package release-date probe (see_probes_release_date). A subclass that sets this string advertises native support (seesupports_cooldown); the value produced bycooldown_env_value()is then injected into the environment of every CLI call.
- windows_creation_flags: int = 0¶
Additional Windows process creation flags OR-ed with
CREATE_NO_WINDOW.Use this on individual managers to control how their subprocess is attached to the calling process’s console. For example, setting this to
subprocess.DETACHED_PROCESS(0x8) fully detaches the child from the parent’s console. Any grandchild process (like a COM server or installer EXE) that callsGenerateConsoleCtrlEvent(0)on exit will then fail silently because there is no console to broadcast to.No-op on non-Windows platforms (
getattrreturns0for Windows-only flags).
- windows_processes_to_cleanup: tuple[str, ...] = ()¶
Windows process image names to forcibly terminate after each CLI call.
When a package manager spawns grandchild processes that outlive the direct subprocess (like winget’s
WindowsPackageManagerServer.exeCOM server), those orphans can linger and consume resources. List the image names here so they are killed aftercommunicate()returns.No-op on non-Windows platforms.
- run_cache: dict[tuple, tuple[int, str, str]] | None = None¶
Optional cache that de-duplicates identical CLI runs across a lane’s managers.
Noneby default, which disables caching: everyrun()call spawns its own subprocess. Two callers install a shared dict, each for the duration of one lane:meta_package_manager.dispatch.dispatch(), on every multi-manager lock-family lane (seemeta_package_manager.dispatch.SHARED_LOCK_FAMILIES), sobrewandcaskboth runningbrew updatefor mpm sync spawn it once.meta_package_manager.dispatch.warm_availability(), on every version-probe lane (seemeta_package_manager.dispatch.merge_into_probe_lanes()), sobrewandcaskboth probingbrew --versionspawn it once too.
The replay still walks
run()’s logging and failure gate, so a failed shared command is attributed to every member. Keyed on the resolved command line, its environment and whether the operation in flight changes the system (see_MUTATING_OPERATIONS), so only genuinely identical invocations collapse.A cached read holds until the lane changes the system. A state-changing command that runs for real first drops every read its lane cached, so the next one spawns again and observes what the command did. The two readings of
ChangeReportdepend on it: both run one command line, and a second reading replayed from the first reports that nothing moved. The results of state-changing commands stay for the whole lane, their replay being the purpose of the cache, and are never served to a read. A command thatmpm --plancaptures ormpm --dry-runsimulates changes nothing, and drops nothing.Caution
Not thread-safe, and it does not need to be: both callers bind a cache to a lane, and
click_extra.execution.run_lanes()runs a lane’s items serially on a single worker. Handing one dict to managers that run concurrently would race two peers into spawning the same command anyway, losing the de-duplication rather than corrupting anything, so the lane is what makes the cache work at all.
- cli_errors: list[CLIError]¶
Accumulate all CLI errors encountered by the package manager.
Every
CLIErrorproduced byrun()lands here, whether or not it is also raised: a failure the caller goes on to swallow (installed_or_empty()and its peers) is still a failure this manager committed, and the end-of-run summary, the serializederrorspayload and the ✓/✘ trail all read this list to say so.Recording only the non-raising half made a manager’s visibility hinge on whether its query happened to pass
must_succeed: mpm list printed “Could not list installed packages.” and still scored the manager ✓, leaving it out of the closing count.A failed
VERSION_PROBEstays out, and is the only thing that does. The probe is a liveness question about whatever binarycli_pathresolved inPATH, not work the user asked for, andunavailable_reasonalready reports its answer. Kept here, it named a manager mpm had correctly dropped in the end-of-run summary of every command, on every host where an unrelated binary shadows a manager’s name: PearCleaner’spearsymlink had mpm outdated warn about a PHP manager the user never installed (issue 2119). A probe that never completed keeps its entry, a timeout or an interrupt being plumbing rather than a verdict: that path appends from_spawnand never reaches the gate.
- property supports_cooldown: bool¶
Whether this manager can enforce a release-age
cooldown.Either natively, by injecting
cooldown_env_varinto every CLI call, or through the per-package release-date probe advertised by_probes_release_date.
- cooldown_env_value()[source]¶
Render
cooldownas the value ofcooldown_env_var.Defaults to the RFC 3339 timestamp of the most recent release date still allowed, i.e. now minus the cooldown. Managers whose environment variable expects another format (a number of minutes, a bare day count, …) override this.
- Return type:
- cooldown_rounded_up(unit_seconds)[source]¶
Render
cooldownas an integer count ofunit_seconds-long units, rounded up.Helper for the
cooldown_env_value()overrides of managers whose native release-age knob expects a unit count rather than the default RFC 3339 timestamp (npm’s day-basedmin-release-age, pnpm’s minute-basedminimumReleaseAge). Sub-unit cooldowns round up so the gate over-protects rather than silently collapsing to0(the “no cooldown” sentinel).- Return type:
- cooldown_env()[source]¶
Environment fragment enforcing the
cooldown, empty when inactive.Returns an empty mapping unless a
cooldownis set and the manager supports it and itscooldown_policyhas not exempted it (off). Merged into the environment of everyrun()call.
- search_all_cli(cli_names, env=None)[source]¶
Search for all binary files matching the CLI names, in all environment path.
This is like our own implementation of
shutil.which(), with the difference that it is capable of returning all the possible paths of the provided file names, in all environment path, not just the first one that match. And on Windows, prevents matching of CLI in the current directory, which takes precedence on other paths.Returns all files matching any
cli_names, by iterating over all folders in this order:folders provided by
cli_search_path,then in all the default places specified by the environment variable (i.e.
os.getenv("PATH")).
Only returns files that exists and are not empty.
Caution
Symlinks are not resolved, because some manager like Homebrew on Linux relies on some sort of symlink-based trickery to set environment variables.
- sibling_cli(name, *, same_dir=False)[source]¶
Resolve the path of a sibling binary of the manager’s main CLI.
Some managers ship as a suite of binaries (
xbps-install/xbps-query,pkg_add/pkg_info, emerge’sqlist): an operation then runs a sibling instead of the main CLI. By default the sibling is searched like the main CLI itself (which(), honoringcli_search_path), and a missing binary raisesFileNotFoundErrorrather than silently falling back to the wrong program.same_dir=Trueinstead takes the sibling from the directory ofcli_path, without an existence probe: suites installing all their binaries side by side (XBPS, Nix) guarantee the neighbor, and resolving it from the same directory can never mix two installations. A genuinely missing file then surfaces at spawn time.- Return type:
- property cli_path: Path | None[source]¶
Fully qualified path to the canonical package manager binary.
Try each CLI names provided by
cli_names, in each system path provided bycli_search_path. In that order. Then returns the first match.Executability of the CLI will be separately assessed later by the
meta_package_manager.execution.CLIExecutor.executableproperty below.
- property version: TokenizedString | None[source]¶
Invoke the manager and extract its own reported version string.
Returns a parsed and normalized version in the form of a
meta_package_manager.version.TokenizedStringinstance.Skipped on platforms where the manager is not supported, even if
cli_pathresolved to an executable: that binary almost certainly belongs to a different tool that happens to share the same name (e.g. GNUmakeon macOS getting matched by the FreeBSDportsmanager), so probing it would either misreport the version or surface confusing error output.
- property subject: str¶
The
manager.operationpath naming what this manager is doing now.Labels every line logged inside an operation (
warning:brew.install:) and names the spinner and the trail line of the same call, so one call reads alike on all three. Outside any operation it is the bare ID: seeoperation_subject(). A line about the manager itself, logged while no operation runs (discovery, selection, capability checks), labels it with the bareidinstead.
- acting_as(operation=None, *, stop_on_error=None)[source]¶
Temporarily adjust the manager’s execution state, restoring it on exit.
operationre-stamps_active_operation(the per-operation timeout and watchdog key) for the duration of the block;Noneleaves the current stamp untouched.stop_on_errorlikewise overrides the failure policy when set: the per-package state changers run their action understop_on_error=Trueso a botched operation raises and is recorded by the caller instead of being silently accumulated.The public seam for callers needing a scoped state override: the CLI layer must never poke
_active_operationorstop_on_errordirectly.
- new_errors()[source]¶
Collect the
cli_errorsthis manager accumulates inside the block.The yielded list is filled when the block exits, whatever the exit: a fan-out task reads it after the operation ran to decide whether the manager failed its
✓/✘trail line. Only the errors of this block are reported, since the pool’s instances live for the whole process and carry the errors of every previous invocation.
- run(*args, extra_env=None, must_succeed=False)[source]¶
Run a shell command, return the output and accumulate error messages.
argsis allowed to be a nested structure of iterables, in which case it will be recursively flatten, thenNonewill be discarded, and finally each item casted to strings.- Running commands with that method takes care of:
disclosing the invocation at
INFO(the reproducible$-prompt line with forced environment variables) and streaming the raw output live toDEBUG, prefixed with the manager ID, viaclick_extra.execution.run_cli()flagging, on a terminal, the call of an internal escalator that goes silent while it may be blocked on a hidden password prompt (see
_hidden_prompt_riskand_StallWatchdog)detaching every other call into its own POSIX session and process group, so a timeout or Ctrl+C reaps the whole process tree and a wedged grandchild cannot linger as an orphan; the flagged call above keeps the controlling terminal so its
sudoprompt stays answerableremoving ANSI escape codes from
subprocess.CompletedProcess.stdoutandsubprocess.CompletedProcess.stderrreturning ready-to-use normalized strings (dedented and stripped)
letting
mpm --dry-runandmpm --stop-on-errorhave expected effect on execution
The exception above has two more members, which both spend the credential cache: a call mpm escalates itself, and the call of an internal escalator that may run its own
sudoon a warm cache (see_spends_warm_cache).sudokeys that cache to the terminal and to its session, which a session of its own would hide.A call mpm escalates itself leads no process group of its own, so its timeout kill reaches the
sudoprocess alone.sudoruns the command under a monitor process wheneveruse_ptyis on, its upstream default since1.9.14, so the manager binary survives the kill: anapt upgradeoutlasting the state-change timeout keeps/var/lib/dpkg/lock-frontend, and the nextaptoraptitudecall fails on a lock mpm already declared finished.Todo
Delete the paragraph above once the click-extra floor reaches the release whose
run_clialso kills a child’s descendants one PID at a time. It reads the process tree before the kill and signals every descendant, which reaps the escalated command with thesudothat spawned it. mpm cannot do this itself: it never sees the child’s PID, and the kill reparents the descendants beforesubprocess.TimeoutExpiredsurfaces, cutting the links a walk follows.- Parameters:
must_succeed (
bool) – ifTrue, raisemeta_package_manager.execution.CLIErrorwhen the command fails, regardless of the user-facingstop_on_errorpreference, rather than accumulating the error for an end-of-run summary. Use for calls whose output is parsed (JSON, XML, regex), where a swallowed failure would be indistinguishable from empty results. A non-zero exit that leaves<stderr>empty, or holding onlystderr_noise, is tolerated as a benign status code (npmandpnpm outdatedexit1when updates exist); only the per-package state changers, which run under a patchedstop_on_error, treat every non-zero exit as a failure. See the failure gate below for details.- Return type:
- build_cli(*args, auto_pre_cmds=True, auto_pre_args=True, auto_post_args=True, override_pre_cmds=None, override_cli_path=None, override_pre_args=None, override_post_args=None, sudo=False)[source]¶
Build the package manager CLI by combining the custom
*argswith the package manager’s global parameters.Returns a tuple of strings.
Helps the construction of CLI’s repeating patterns and makes the code easier to read. Just pass the specific
*argsand the full CLI string will be composed out of the globals, following this schema:$ [<pre_cmds>|sudo --non-interactive] <cli_path> <pre_args> <*args> <post_args>
self.pre_cmdsis added before the CLI path.self.cli_pathis used as the main binary to execute.self.pre_argsandself.post_argsglobals are added before and after the provided*args.
Each additional set of elements can be disabled with their respective flag:
auto_pre_cmds=Falseto skip the automatic addition ofself.pre_cmdsauto_pre_args=Falseto skip the automatic addition ofself.pre_argsauto_post_args=Falseto skip the automatic addition ofself.post_args
Each global set of elements can be locally overridden with:
override_pre_cmds=tuple()override_cli_path=stroverride_pre_args=tuple()override_post_args=tuple()
On UNIX, an operation marked privileged (
sudo=True) is escalated only when the per-manager policy opts in (sudo, falling back todefault_sudo). It is then run through sudo with--non-interactive(it spends the credential cache warmed byprime_sudo()and fails fast rather than blocking on a password prompt). When escalation applies,override_pre_cmdsis not allowed to be set andauto_pre_cmdsis forced toFalse. A non-UNIX host never escalates.
- run_cli(*args, auto_extra_env=True, auto_pre_cmds=True, auto_pre_args=True, auto_post_args=True, override_extra_env=None, override_pre_cmds=None, override_cli_path=None, override_pre_args=None, override_post_args=None, force_exec=False, must_succeed=False, sudo=False)[source]¶
Build and run the package manager CLI by combining the custom
*argswith the package manager’s global parameters.After the CLI is built with the
meta_package_manager.execution.CLIExecutor.build_cli()method, it is executed with themeta_package_manager.execution.CLIExecutor.run()method, augmented with environment variables fromself.extra_env.All parameters are the same as
meta_package_manager.execution.CLIExecutor.build_cli(), plus:auto_extra_env=Falseto skip the automatic addition ofself.extra_envoverride_extra_env=dict()to locally overrides the laterforce_execignores thempm --dry-run,mpm --stop-on-errorandmpm --planoptions to force the execution and completion of the command. It is used for reads whose output is needed regardless (version detection,yarn global dir), which must run for real even when the user asked to simulate or to only plan mutations.must_succeedraises on non-zero exit regardless ofmpm --stop-on-error. Seerun()for details.
- Return type: