Files
ethernet b16f953f7c fix(pm): re-activate from the shebang only when an input's mtime differs
The _hermes-python prologue compared uv.lock / pyproject.toml / pm/lock.json
against facts.json with `-nt`. facts.json is only rewritten on a real sync,
so a checkout that rewrites an input without changing it left the input
newer forever: every run from an activated shell re-sourced activate
(~370ms instead of ~13ms).

pm now records, after every successful full-closure install (no-op syncs
included), a stamp per input under installs/<key>/inputs carrying the exact
mtime the install was verified against, snapshotted before installing so
an input edited mid-install still reads as stale. The prologue re-activates
when any input's mtime differs from its stamp in either direction (branch
switches can move it backwards), when an input is missing, or when there
are no stamps. It globs the stamp dir, so pm owns the only input list.

Also read pyvenv.cfg as utf-8-sig (footgun lane, same file).
2026-09-23 13:38:54 -04:00

95 lines
4.0 KiB
Python

"""Activation guard for repository scripts.
Every script in this checkout assumes it runs under the PM-activated
environment (``source ./activate`` on POSIX, ``. .\\activate.ps1`` on Windows),
which is what puts the pinned interpreter and its dependency tree on
``PATH``/``PYTHONPATH``. A script run with a bare system Python instead fails
much later with a confusing ``ModuleNotFoundError``. Call
:func:`require_activation` first and it fails immediately, naming the exact
command for the caller's shell.
Import it by its bare name: ``scripts/`` is the running script's own directory,
so it is on ``sys.path`` whether or not the shell is activated::
from _activation import require_activation
require_activation()
This module must stay stdlib-only and side-effect free at import — it has to
load before the environment it checks for exists.
Activating from the shebang
---------------------------
A POSIX script can activate for itself, so ``./scripts/foo.py`` works from any
cwd with no manual ``source``. The shebang points at ``scripts/_hermes-python``,
a real repo script that sources ``activate`` and execs the interpreter on the
same file::
#!/usr/bin/env -S bash -c 'exec "$BASH" "$(dirname "$0")/_hermes-python" "$0" "$@"'
The kernel appends the invoking script as the last argument, and ``bash -c``
binds it to ``$0`` — so the prologue finds the target relative to itself and
the shebang stays independent of the cwd. ``$BASH`` for the second hop keeps it
free of both the exec bit and a ``PATH`` lookup.
Activation is not re-run when the inherited environment is still current. pm
records one stamp per dependency input (``uv.lock``, ``pyproject.toml``,
``pm/lock.json``) beside the installed-state file that ``__HERMES_ACTIVATED``
names, each carrying the exact mtime that input had when the install was last
verified against it (``pm.environments.record_activation_inputs``). The
prologue re-activates when any input's mtime *differs* from its stamp: newer
or older, since switching branches can move it either way. ``[ -nt ]`` and
``[ -ot ]`` are bash builtins, so the check costs no process spawn. Every
successful activation records again, including no-op syncs, so a checkout that
touches an input without changing it costs one re-activation and then settles.
Two constraints worth knowing: the line is 83 bytes (a shebang must stay under
127), and ``/usr/bin/env -S`` is GNU and newer-BSD — verify it if an older
macOS or BSD is a target. macOS's stock bash 3.2 compares whole seconds, so an
input rewritten within the same second as its recorded mtime goes unnoticed
there. Windows keeps using ``python scripts\\foo.py`` with the guard.
"""
from __future__ import annotations
import os
import sys
ACTIVATION_ENV_VAR = "__HERMES_ACTIVATED"
POSIX_COMMAND = "source ./activate"
WINDOWS_COMMAND = ". .\\activate.ps1"
def activation_command() -> str:
"""The exact command that activates this checkout in the caller's shell."""
if os.name != "nt":
return POSIX_COMMAND
# A bash-family shell on Windows (Git Bash, MSYS, WSL interop) sources the
# POSIX script; only a native host gets the PowerShell one.
if os.environ.get("MSYSTEM") or os.path.basename(os.environ.get("SHELL", "")) in {"bash", "sh", "zsh"}:
return POSIX_COMMAND
return WINDOWS_COMMAND
def require_activation() -> None:
"""Exit the process unless the launching shell sourced the activate script.
Only tests the sentinel for non-emptiness: its value is the installed-state
path the environment was built from (and earlier revisions wrote a bare
``1``), so any value means "an ancestor shell activated".
"""
if os.environ.get(ACTIVATION_ENV_VAR):
return
script = os.path.basename(sys.argv[0]) or "this script"
print(
f"{script}: the Hermes environment is not activated.\n"
"From the repository root, run:\n"
"\n"
f" {activation_command()}\n"
"\n"
"then re-run this script.",
file=sys.stderr,
)
raise SystemExit(1)