Repo scripts assume the PM-activated environment, so running one without activation fails much later with a confusing ImportError. Add the two halves covering both invocation paths: - scripts/_activation.py: require_activation() exits immediately, naming the exact command for the caller's shell (source ./activate on POSIX, . .\activate.ps1 on a native Windows host), before any heavy import. - scripts/_hermes-python: the POSIX shebang target. `#!/usr/bin/env -S bash -c '...'` hands itself the target path through bash -c's $0, sources activate, then execs the interpreter on the same file -- so tracebacks and __file__ still point at the real script and ./scripts/foo.py works from any cwd with no manual source. __HERMES_ACTIVATED changes from a bare "1" to the installed-state file the environment was composed against, so one value carries activation, which checkout activated it, and a staleness stamp. The prologue compares that file against uv.lock / pyproject.toml / pm/lock.json with the `-nt` builtin -- no process spawn -- and re-activates once when the inherited environment predates its inputs. pm rewrites that file only on a real sync, so the check settles back to current rather than re-syncing on every run. A legacy "1" keeps working: require_activation() tests non-emptiness, and the prologue's [ -e ] fails on it, so it activates once and upgrades.
51 lines
1.8 KiB
Bash
Executable File
51 lines
1.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Run a repo Python script under the activated Hermes environment.
|
|
#
|
|
# Shebang target for POSIX scripts:
|
|
# #!/usr/bin/env -S bash -c 'exec "$BASH" "$(dirname "$0")/_hermes-python" "$0" "$@"'
|
|
#
|
|
# The kernel appends the invoking script, so `$1` is the Python file to run
|
|
# and `$@` its arguments. Activates when there is no environment to inherit, or
|
|
# when the inherited one predates its inputs; then execs the interpreter on the
|
|
# same file, so tracebacks and __file__ point at the real script.
|
|
set -u
|
|
|
|
target=${1:-}
|
|
if [ -z "$target" ]; then
|
|
printf 'usage: %s <script.py> [args...]\n' "$0" >&2
|
|
exit 2
|
|
fi
|
|
shift
|
|
|
|
_here="$(cd "$(dirname "$target")" && pwd)"
|
|
while [ "$_here" != / ] && [ ! -f "$_here/activate" ]; do _here="$(dirname "$_here")"; done
|
|
if [ ! -f "$_here/activate" ]; then
|
|
printf '%s: no activate script above %s\n' "$0" "$target" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# __HERMES_ACTIVATED holds the installed-state file the environment was built
|
|
# against, so `-nt` against the inputs that decide the dependency set answers
|
|
# "is the inherited environment stale". `[ -nt ]` is a bash builtin — no
|
|
# process spawn — and pm rewrites that file on every real sync while no-oping
|
|
# otherwise, so one re-activation settles this back to current rather than
|
|
# re-syncing on every run. A path that no longer exists (or a legacy literal
|
|
# "1") fails `-e` and activates.
|
|
_needs_activation=1
|
|
if [ -n "${__HERMES_ACTIVATED:-}" ] && [ -e "${__HERMES_ACTIVATED}" ]; then
|
|
_needs_activation=0
|
|
for _input in uv.lock pyproject.toml pm/lock.json; do
|
|
if [ "$_here/$_input" -nt "$__HERMES_ACTIVATED" ]; then
|
|
_needs_activation=1
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ "$_needs_activation" = 1 ]; then
|
|
# shellcheck source=/dev/null
|
|
. "$_here/activate" || exit 1
|
|
fi
|
|
|
|
exec python3 "$target" "$@"
|