`activate` doubled as the thing scripts sourced non-interactively, so it carried `--` argument guards and every caller had to tolerate its prompt and `hermes` function. Composition (setup sync, bootstrap Python, pm env) now lives in scripts/_activation.sh, and there are two consumers: - `activate`: interactive, reversible (`deactivate`). - new `scripts/run-in-hermes-env CMD...`: applies the env to one process and execs into it. Replaces `scripts/_hermes-python`; every Python script's shebang and `scripts/run_tests.sh` (which re-executes itself under it, however it was launched) use it. `pm.environments --format sh` emits the env as shell exports, so the inline JSON-to-export Python is gone from `activate`. An install that did not build the test environment now reads as stale for every consumer. Tests are split along the same seams: activation support fixtures, bash/ps1 activation, env-script quoting, and the runner's staleness.
127 lines
5.4 KiB
Bash
127 lines
5.4 KiB
Bash
# Sourced, never executed: how a POSIX shell gets the Hermes dev environment.
|
|
# `activate` (interactive, reversible) and `scripts/run-in-hermes-env` (a child
|
|
# process, one-way) both build on it; neither owns any of it.
|
|
#
|
|
# hermes_sync REPO [FLAG] bring the install up to date (setup-hermes.sh)
|
|
# hermes_compose_env REPO print the composed environment as an `sh` script
|
|
# hermes_apply_env SCRIPT evaluate that script into this shell
|
|
# hermes_activation_current REPO is the inherited environment still REPO's?
|
|
# hermes_ensure_env REPO sync, compose and apply unless it is current
|
|
#
|
|
# pm records, beside the installed-state file named by __HERMES_ACTIVATED, one
|
|
# stamp per dependency input carrying the exact mtime that input had when the
|
|
# install was last verified against it (pm.environments.record_activation_inputs).
|
|
# Any input whose mtime DIFFERS from its stamp (newer or older, since a branch
|
|
# switch can move it either way) means the inherited environment may not match
|
|
# its inputs. `-nt`/`-ot` are bash builtins, so the check costs no process
|
|
# spawn, and every successful sync re-records, so one re-sync settles it.
|
|
# Missing stamps or inputs, a dangling sentinel or a literal "1" all read as
|
|
# stale, and so does an install that did not build the test environment:
|
|
# every environment this library composes includes it.
|
|
|
|
# hermes_sync REPO [--test-environment[=EXTRAS]]: run setup in a child, so a
|
|
# failure cannot exit or half-change the caller's shell and setup never
|
|
# republishes launchers or shell config. Progress goes to stderr.
|
|
hermes_sync() {
|
|
local repo="$1" test_environment="${2:---test-environment}"
|
|
(
|
|
unset PYTHONHOME PYTHONPATH VIRTUAL_ENV
|
|
bash "$repo/setup-hermes.sh" --runtime-only "$test_environment"
|
|
) >&2
|
|
}
|
|
|
|
# hermes_bootstrap_python REPO: print the interpreter that can run pm before
|
|
# any dependency is importable. It only emits the environment; it installs nothing.
|
|
hermes_bootstrap_python() {
|
|
local repo="$1" store candidate
|
|
for candidate in "$repo/.venv/bin/python" "$repo/.venv/Scripts/python.exe" \
|
|
"$repo/venv/bin/python" "$repo/venv/Scripts/python.exe"; do
|
|
[ -x "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
done
|
|
for store in "${HERMES_RUNTIME_DIR:-}" "$repo/../tools" "${HERMES_HOME:-$HOME/.hermes}/tools"; do
|
|
[ -n "$store" ] || continue
|
|
for candidate in "$store"/python-*/bin/python3 "$store"/python-*/python.exe \
|
|
"$store"/python-*/bin/python "$store"/python-*/bin/python.exe; do
|
|
[ -x "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# hermes_compose_env REPO: print the environment of the installed state as an
|
|
# `sh` script.
|
|
hermes_compose_env() {
|
|
local repo="$1" python script
|
|
python="$(hermes_bootstrap_python "$repo")" || {
|
|
echo "no bootstrap Python found; run setup-hermes.sh" >&2
|
|
return 1
|
|
}
|
|
script="$(PYTHONHOME= PYTHONPATH="$repo" "$python" -m pm.environments --format sh)" &&
|
|
[ -n "$script" ] || {
|
|
echo "could not read pm env (run ./setup-hermes.sh first)" >&2
|
|
return 1
|
|
}
|
|
printf '%s\n' "$script"
|
|
}
|
|
|
|
# hermes_apply_env SCRIPT: evaluate a script from `hermes_compose_env REPO`.
|
|
hermes_apply_env() {
|
|
# Resolved before the eval replaces PATH.
|
|
local cygpath name
|
|
cygpath="$(command -v cygpath 2>/dev/null || :)"
|
|
eval "$1"
|
|
# The MSYS/Cygwin runtime hands a native Windows Python these variables in
|
|
# Windows form (`C:\a;C:\b`) and converts them back only for its own
|
|
# children. Exported verbatim, bash splits PATH on ':' and finds no commands.
|
|
if [ -n "$cygpath" ]; then
|
|
PATH="$("$cygpath" -u -p "$PATH")"
|
|
for name in HOME TMPDIR TMP TEMP; do
|
|
if [ -n "${!name-}" ]; then
|
|
printf -v "$name" '%s' "$("$cygpath" -u "${!name}")"
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
# hermes_activation_current REPO: status 0 when the inherited environment
|
|
# matches REPO's current inputs.
|
|
hermes_activation_current() {
|
|
local repo="$1" sentinel stamps stamp input stamped=0
|
|
[ -n "${__HERMES_ACTIVATED:-}" ] && [ -e "${__HERMES_ACTIVATED}" ] || return 1
|
|
# activate.ps1 records a Windows path; Git Bash accepts it with slashes.
|
|
sentinel="${__HERMES_ACTIVATED//\\//}"
|
|
stamps="${sentinel%/*}/inputs"
|
|
[ -f "$stamps/.project-root" ] && [ -f "$stamps/.test-environment" ] || return 1
|
|
local owner="$repo" recorded
|
|
recorded="$(< "$stamps/.project-root")"
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
owner="$(cygpath -am "$repo")" || return 1
|
|
recorded="${recorded//\\//}"
|
|
[ "${owner,,}" = "${recorded,,}" ] || return 1
|
|
else
|
|
[ "$(cd "$owner" && pwd -P)" = "$recorded" ] || return 1
|
|
fi
|
|
for stamp in "$stamps"/* "$stamps"/*/*; do
|
|
[ -f "$stamp" ] || continue
|
|
stamped=1
|
|
input="$repo/${stamp#"$stamps"/}"
|
|
if [ ! -e "$input" ] || [ "$input" -nt "$stamp" ] || [ "$input" -ot "$stamp" ]; then
|
|
return 1
|
|
fi
|
|
done
|
|
[ "$stamped" = 1 ]
|
|
}
|
|
|
|
# hermes_ensure_env REPO: leave this shell with REPO's environment, doing
|
|
# nothing when the inherited one is current.
|
|
hermes_ensure_env() {
|
|
local repo="$1" script
|
|
hermes_activation_current "$repo" && return 0
|
|
hermes_sync "$repo" || {
|
|
echo "setup failed" >&2
|
|
return 1
|
|
}
|
|
script="$(hermes_compose_env "$repo")" || return 1
|
|
hermes_apply_env "$script"
|
|
}
|