The pm env is read by a native Windows Python, which the MSYS/Cygwin runtime hands PATH, HOME, TMPDIR, TMP and TEMP in Windows form. activate exported them verbatim, so bash split 'C:\a;C:\b' on ':' and found no commands: scripts/run_tests.sh died with 'env: command not found' right after activation. Convert them back with cygpath, resolved before the export replaces PATH.
226 lines
9.3 KiB
Bash
Executable File
226 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# venv-style activation for the Hermes dev environment (pm-managed tools).
|
|
#
|
|
# source ./activate (bash / zsh, repo root)
|
|
# .\activate.ps1 (PowerShell)
|
|
#
|
|
# Emits the composed pm env (PATH + tool vars) into the CURRENT shell, with
|
|
# save/restore: `deactivate` undoes exactly what activation changed.
|
|
#
|
|
# `hermes` becomes a shell function for this checkout. It runs only while the
|
|
# shell is inside this worktree and refuses outside it, so a sibling worktree
|
|
# keeps its own command. A prompt prefix names the worktree.
|
|
#
|
|
# Sync through setup before selecting the environment. PM owns freshness;
|
|
# activation does not maintain a second dependency stamp. It trusts the
|
|
# recorded tool digest instead of re-hashing every entry. `hermes pm
|
|
# install` and `hermes update` keep that check.
|
|
# ============================================================================
|
|
_HERMES_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Options: `--test-extras a,b` selects the test environment's runtime extras
|
|
# (default: [all]); `--` ends options.
|
|
# A bare `source ./activate` inherits the caller's positional parameters.
|
|
# Ignore unrelated words so scripts with their own arguments can source it.
|
|
_hermes_test_env="--test-environment"
|
|
_hermes_expect_extras=""
|
|
for _hermes_arg in "$@"; do
|
|
if [ -n "$_hermes_expect_extras" ]; then
|
|
_hermes_test_env="--test-environment=$_hermes_arg"
|
|
_hermes_expect_extras=""
|
|
continue
|
|
fi
|
|
case "$_hermes_arg" in
|
|
--) break ;;
|
|
--test-extras) _hermes_expect_extras=1 ;;
|
|
--test-extras=*) _hermes_test_env="--test-environment=${_hermes_arg#--test-extras=}" ;;
|
|
*) : ;;
|
|
esac
|
|
done
|
|
if [ -n "$_hermes_expect_extras" ]; then
|
|
printf '%s\n' 'activate: --test-extras needs a comma-separated list' >&2
|
|
unset _hermes_arg _hermes_test_env _hermes_expect_extras
|
|
return 2 2>/dev/null || exit 2
|
|
fi
|
|
# Setup runs in a child: failures must not exit or partially activate the
|
|
# caller's shell, and activation must not republish launchers or shell config.
|
|
if ! (
|
|
unset PYTHONHOME PYTHONPATH VIRTUAL_ENV
|
|
bash "$_HERMES_REPO/setup-hermes.sh" --runtime-only "$_hermes_test_env"
|
|
) >&2; then
|
|
printf '%s\n' 'activate: setup failed; shell environment unchanged' >&2
|
|
unset _hermes_arg _hermes_test_env _hermes_expect_extras
|
|
return 1 2>/dev/null || exit 1
|
|
fi
|
|
unset _hermes_arg _hermes_test_env _hermes_expect_extras
|
|
|
|
# Guard against double-sourcing: venv activate precedent (re-activating is a
|
|
# no-op re-save; we keep it idempotent by deactivating first). Key on the
|
|
# function, not the sentinel: child shells inherit __HERMES_ACTIVATED from an
|
|
# activated parent but never the function, and there is nothing to undo there.
|
|
if [ "$(type -t deactivate 2>/dev/null)" = "function" ]; then
|
|
deactivate
|
|
fi
|
|
|
|
# Bootstrap Python only emits the environment; it does not install anything.
|
|
_hermes_repo="$_HERMES_REPO"
|
|
_hermes_py=""
|
|
for _hermes_candidate in "$_hermes_repo/.venv/bin/python" "$_hermes_repo/.venv/Scripts/python.exe" \
|
|
"$_hermes_repo/venv/bin/python" "$_hermes_repo/venv/Scripts/python.exe"; do
|
|
[ -x "$_hermes_candidate" ] && { _hermes_py="$_hermes_candidate"; break; }
|
|
done
|
|
if [ -z "$_hermes_py" ]; then
|
|
for _hermes_store in "${HERMES_RUNTIME_DIR:-}" "$_hermes_repo/../tools" "${HERMES_HOME:-$HOME/.hermes}/tools"; do
|
|
[ -n "$_hermes_store" ] || continue
|
|
for _hermes_candidate in "$_hermes_store"/python-*/bin/python3 "$_hermes_store"/python-*/python.exe "$_hermes_store"/python-*/bin/python "$_hermes_store"/python-*/bin/python.exe; do
|
|
[ -x "$_hermes_candidate" ] && { _hermes_py="$_hermes_candidate"; break; }
|
|
done
|
|
[ -n "$_hermes_py" ] && break
|
|
done
|
|
fi
|
|
if [ -z "$_hermes_py" ]; then
|
|
echo "activate: no bootstrap Python found; run setup-hermes.sh" >&2
|
|
return 1 2>/dev/null || exit 1
|
|
fi
|
|
|
|
# --- emit export lines from `pm env` ---
|
|
_hermes_json="$(PYTHONHOME= PYTHONPATH="$_hermes_repo" "$_hermes_py" -m pm.environments)"
|
|
if [ -z "$_hermes_json" ] || [ "${_hermes_json#*{}" = "$_hermes_json" ]; then
|
|
echo "activate: could not read pm env (run ./setup-hermes.sh first)" >&2
|
|
unset _hermes_repo _hermes_os _hermes_arch _hermes_target _hermes_store _hermes_py _hermes_json
|
|
return 1 2>/dev/null || exit 1
|
|
fi
|
|
|
|
_hermes_keys="$(printf '%s' "$_hermes_json" | "$_hermes_py" -c 'import json,sys; print(" ".join(json.load(sys.stdin)))' \
|
|
| tr ' ' '\n' | grep -E '^[A-Za-z_][A-Za-z0-9_]*$' | tr '\n' ' ')"
|
|
[ -n "$_hermes_keys" ] || _hermes_keys="PATH"
|
|
|
|
# --- snapshot what we are about to change (deactivate restores this) ---
|
|
# __HERMES_ACTIVATED is part of the composed env below (it carries the
|
|
# installed-state path this environment was built from), so it is snapshotted
|
|
# and exported with every other key — no separate assignment here.
|
|
__HERMES_SAVED_PATH="$PATH"
|
|
for _hermes_k in $_hermes_keys; do
|
|
eval "__HERMES_SAVED_$_hermes_k=\"\${$_hermes_k+set}\""
|
|
eval "__HERMES_PRIOR_$_hermes_k=\${$_hermes_k-__HERMES_UNSET__}"
|
|
done
|
|
|
|
# Resolved before the export below replaces PATH.
|
|
_hermes_cygpath="$(command -v cygpath 2>/dev/null || :)"
|
|
|
|
eval "$(printf '%s' "$_hermes_json" | "$_hermes_py" -c '
|
|
import json, sys, shlex, re
|
|
for k, v in json.load(sys.stdin).items():
|
|
# Windows carries names like ProgramFiles(ARM) that are not valid
|
|
# bash identifiers — skip them (they pass through untouched).
|
|
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", k):
|
|
continue
|
|
print("export %s=%s" % (k, shlex.quote(str(v))))')"
|
|
|
|
# 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 "$_hermes_cygpath" ]; then
|
|
PATH="$("$_hermes_cygpath" -u -p "$PATH")"
|
|
for _hermes_k in HOME TMPDIR TMP TEMP; do
|
|
if [ -n "${!_hermes_k-}" ]; then
|
|
printf -v "$_hermes_k" '%s' "$("$_hermes_cygpath" -u "${!_hermes_k}")"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# This checkout, not whichever `hermes` PATH finds. A function beats PATH, an
|
|
# alias, and a hashed command, including the MSIX execution alias.
|
|
__HERMES_WORKTREE="$_hermes_repo"
|
|
# The branch names the worktree. A checkout cannot share a branch with another.
|
|
__HERMES_WORKTREE_NAME="$(git -C "$_hermes_repo" rev-parse --abbrev-ref HEAD 2>/dev/null || :)"
|
|
if [ -z "$__HERMES_WORKTREE_NAME" ] || [ "$__HERMES_WORKTREE_NAME" = HEAD ]; then
|
|
__HERMES_WORKTREE_NAME="$(basename "$_hermes_repo")"
|
|
fi
|
|
__HERMES_SAVED_PS1="${PS1-__HERMES_UNSET__}"
|
|
__HERMES_SAVED_PROMPT_COMMAND="${PROMPT_COMMAND-__HERMES_UNSET__}"
|
|
|
|
_hermes_worktree_here() {
|
|
_hermes_top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
|
|
[ "$(cd "$_hermes_top" && pwd -P)" = "$(cd "$__HERMES_WORKTREE" && pwd -P)" ]
|
|
}
|
|
|
|
hermes() {
|
|
_hermes_worktree_here || {
|
|
printf '%s\n' "hermes: $(pwd) is outside $__HERMES_WORKTREE; refusing (the installed command is hidden while this checkout is active)" >&2
|
|
return 1
|
|
}
|
|
(
|
|
cd "$__HERMES_WORKTREE" || exit 1
|
|
if [ -n "${PYTHON-}" ]; then
|
|
exec "$PYTHON" hermes "$@"
|
|
elif [ -x .venv/Scripts/python.exe ]; then
|
|
exec .venv/Scripts/python.exe hermes "$@"
|
|
elif [ -x .venv/bin/python ]; then
|
|
exec .venv/bin/python hermes "$@"
|
|
elif [ -x venv/Scripts/python.exe ]; then
|
|
exec venv/Scripts/python.exe hermes "$@"
|
|
elif [ -x venv/bin/python ]; then
|
|
exec venv/bin/python hermes "$@"
|
|
else
|
|
exec python hermes "$@"
|
|
fi
|
|
)
|
|
}
|
|
|
|
_hermes_prompt() {
|
|
if [ "$__HERMES_SAVED_PS1" = "__HERMES_UNSET__" ]; then
|
|
_hermes_base_ps1=""
|
|
else
|
|
_hermes_base_ps1="$__HERMES_SAVED_PS1"
|
|
fi
|
|
if _hermes_worktree_here; then
|
|
PS1="($__HERMES_WORKTREE_NAME) $_hermes_base_ps1"
|
|
else
|
|
PS1="$_hermes_base_ps1"
|
|
fi
|
|
}
|
|
|
|
if [ -n "${PROMPT_COMMAND-}" ]; then
|
|
PROMPT_COMMAND="_hermes_prompt; ${PROMPT_COMMAND}"
|
|
else
|
|
PROMPT_COMMAND=_hermes_prompt
|
|
fi
|
|
_hermes_prompt
|
|
|
|
deactivate() {
|
|
export PATH="$__HERMES_SAVED_PATH"
|
|
for _hermes_k in $__HERMES_KEY_LIST; do
|
|
eval "_hermes_was=\"\$__HERMES_SAVED_$_hermes_k\""
|
|
eval "_hermes_old=\"\$__HERMES_PRIOR_$_hermes_k\""
|
|
if [ "$_hermes_was" != "set" ]; then
|
|
unset "$_hermes_k"
|
|
else
|
|
export "$_hermes_k=$_hermes_old"
|
|
fi
|
|
unset "__HERMES_SAVED_$_hermes_k" "__HERMES_PRIOR_$_hermes_k"
|
|
done
|
|
if [ "$__HERMES_SAVED_PS1" = "__HERMES_UNSET__" ]; then
|
|
unset PS1
|
|
else
|
|
PS1="$__HERMES_SAVED_PS1"
|
|
fi
|
|
if [ "$__HERMES_SAVED_PROMPT_COMMAND" = "__HERMES_UNSET__" ]; then
|
|
unset PROMPT_COMMAND
|
|
else
|
|
PROMPT_COMMAND="$__HERMES_SAVED_PROMPT_COMMAND"
|
|
fi
|
|
unset __HERMES_SAVED_PATH __HERMES_KEY_LIST __HERMES_ACTIVATED \
|
|
__HERMES_WORKTREE __HERMES_WORKTREE_NAME \
|
|
__HERMES_SAVED_PS1 __HERMES_SAVED_PROMPT_COMMAND
|
|
unset -f deactivate hermes _hermes_worktree_here _hermes_prompt
|
|
unset _hermes_k _hermes_was _hermes_old _hermes_top
|
|
}
|
|
|
|
# remember the key list for deactivate (kept out of the loop vars above)
|
|
__HERMES_KEY_LIST="$_hermes_keys"
|
|
|
|
unset _hermes_repo _hermes_os _hermes_arch _hermes_target _hermes_store \
|
|
_hermes_py _hermes_json _hermes_keys _hermes_k _hermes_entry _hermes_venvpy \
|
|
_hermes_winarch _hermes_top _hermes_cygpath
|