feat: preflight - prerequisites checked and tool paths recorded before wikitool runs; launcher refuses without it (#151, POSIX half)
Files changed: - .claude/settings.json - .gitea/workflows/ci.yml - .gitea/workflows/nightly.yml - .gitea/workflows/release.yml - .gitea/workflows/tracker-live.yml - .github/hooks/wiki-trace.json - .gitignore - .vibe/hooks.toml - AGENTS.md - CHANGES.md - EVALS.md - INSTALL.md - README.md - VERSION - instructions/bootstrap.md - instructions/preflight.md - instructions/setup-instance.md - instructions/upgrade-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/cli.py - tools/chemenu/commands/dist_cmd.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/git_publish.py - tools/chemenu/commands/migrate_cmd.py - tools/chemenu/config.py - tools/chemenu/corpus_cache.py - tools/chemenu/prerequisites.py - tools/chemenu/search/ripgrep.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_dist_upgrade.py - tools/chemenu/tests/test_doctor.py - tools/chemenu/tests/test_preflight.py - tools/chemenu/toolpaths.py - tools/preflight.sh - tools/prerequisites.txt - tools/run_wikitool.py - tools/trace-hook - tools/wikitool
This commit is contained in:
1 parent
5a731729f6
commit
e4b2b6d9b1
40 files changed
+1849
-125
No files matched your search
Executable
+520
@@ -0,0 +1,520 @@
|
||||
#!/bin/sh
|
||||
# Preflight: check that this machine has what the stack needs, record where each
|
||||
# tool lives, and set up tools/.venv - before any `wikitool` command can run.
|
||||
#
|
||||
# tools/preflight.sh check, record, set up
|
||||
# tools/preflight.sh --set rg=/opt/rg/rg use a path the user named
|
||||
#
|
||||
# Exit 0: everything is in place and .wikitool-tools.json is complete.
|
||||
# Exit 42: the user has to act. The output says what, why, the command that fixes
|
||||
# it and what happens next; show it verbatim and wait (AGENTS.md § Tool
|
||||
# error contract). Never install anything on the user's behalf.
|
||||
# Exit 1: this script was called wrongly, or the tree next to it is incomplete.
|
||||
#
|
||||
# POSIX sh on purpose: it has to run before Python is known to exist, under dash,
|
||||
# bash and the Git Bash that Claude Code uses on Windows. The PowerShell
|
||||
# counterpart is tools/preflight.ps1; what both check is tools/prerequisites.txt.
|
||||
# The procedure around this script: instructions/preflight.md.
|
||||
#
|
||||
# Two variables exist for the test suite and are read nowhere else:
|
||||
# CHEMENU_PREFLIGHT_PLATFORM (windows|macos|linux) and
|
||||
# CHEMENU_PREFLIGHT_LONGPATHS (0|1) stand in for `uname` and the registry.
|
||||
|
||||
set -u
|
||||
|
||||
DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)
|
||||
ROOT=$(dirname -- "$DIR")
|
||||
MANIFEST="$DIR/prerequisites.txt"
|
||||
TOOLS_FILE="$ROOT/.wikitool-tools.json"
|
||||
VENV="$DIR/.venv"
|
||||
REQUIREMENTS="$DIR/requirements.txt"
|
||||
STAMP="$VENV/.chemenu-requirements.sha256"
|
||||
SELF="tools/preflight.sh"
|
||||
|
||||
PYPROBE='import sys; print("%d.%d" % sys.version_info[:2]); print(sys.executable)'
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
usage: tools/preflight.sh [--set <tool>=<path>]...
|
||||
|
||||
Checks the tools this stack needs (tools/prerequisites.txt), records their paths
|
||||
in .wikitool-tools.json and sets up tools/.venv. Exit 0 means ready; exit 42
|
||||
means the user has to act - the output says how.
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- arguments ----------------------------------------------------------------
|
||||
|
||||
SETS=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--set)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "preflight: --set needs <tool>=<path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
SETS="$SETS
|
||||
$2"
|
||||
shift
|
||||
;;
|
||||
--set=*) SETS="$SETS
|
||||
${1#--set=}" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "preflight: unknown argument: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "preflight: $MANIFEST is missing - this script has to run from inside an unpacked stack tree." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- platform -----------------------------------------------------------------
|
||||
|
||||
if [ -n "${CHEMENU_PREFLIGHT_PLATFORM:-}" ]; then
|
||||
PLATFORM=$CHEMENU_PREFLIGHT_PLATFORM
|
||||
else
|
||||
case "$(uname -s 2>/dev/null)" in
|
||||
MINGW*|MSYS*|CYGWIN*) PLATFORM=windows ;;
|
||||
Darwin) PLATFORM=macos ;;
|
||||
*) PLATFORM=linux ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# A path as the rest of the machine spells it: under Git Bash `/c/Tools/rg.exe`
|
||||
# becomes `C:\Tools\rg.exe`, which is what Python and PowerShell read back.
|
||||
native_path() {
|
||||
if [ "$PLATFORM" = windows ] && command -v cygpath >/dev/null 2>&1; then
|
||||
cygpath -w "$1"
|
||||
else
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- problems and the guidance block --------------------------------------------
|
||||
|
||||
PROBLEMS=0
|
||||
GUIDE=""
|
||||
BAD_SET=0
|
||||
|
||||
# problem <what> <why> <fix, may span lines>
|
||||
problem() {
|
||||
PROBLEMS=$((PROBLEMS + 1))
|
||||
fix=$(printf '%s\n' "$3" | sed '2,$s/^/ /')
|
||||
GUIDE="$GUIDE
|
||||
$PROBLEMS) $1
|
||||
Why: $2
|
||||
Fix: $fix
|
||||
Next: Tell the agent once this is done - it runs this check again.
|
||||
"
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [ "$PROBLEMS" -eq 1 ]; then
|
||||
noun="1 thing needs"
|
||||
else
|
||||
noun="$PROBLEMS things need"
|
||||
fi
|
||||
echo ""
|
||||
echo "STOP - $noun your attention before this wiki can run."
|
||||
echo "(Agent: show this output to the user exactly as it is, then wait. Do not install"
|
||||
echo "anything yourself and do not work around it.)"
|
||||
printf '%s' "$GUIDE"
|
||||
exit 42
|
||||
}
|
||||
|
||||
# --- the manifest ---------------------------------------------------------------
|
||||
|
||||
manifest_field() { # <kind> <name> <field number, 1-based>
|
||||
while IFS='|' read -r kind name rest; do
|
||||
case "$kind" in ''|'#'*) continue ;; esac
|
||||
if [ "$kind" = "$1" ] && [ "$name" = "$2" ]; then
|
||||
printf '%s|%s|%s\n' "$kind" "$name" "$rest" | cut -d'|' -f"$3"
|
||||
return 0
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
return 1
|
||||
}
|
||||
|
||||
TOOLS=""
|
||||
while IFS='|' read -r kind name minimum platforms rest; do
|
||||
case "$kind" in ''|'#'*) continue ;; esac
|
||||
[ "$kind" = tool ] || continue
|
||||
case "$name" in *[!a-z0-9]*|'') echo "preflight: bad tool name '$name' in $MANIFEST" >&2; exit 1 ;; esac
|
||||
if [ "$platforms" = all ] || [ "$platforms" = "$PLATFORM" ]; then
|
||||
TOOLS="$TOOLS $name"
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
|
||||
# The install command for this machine's package manager, or every known one.
|
||||
install_hint() { # <tool>
|
||||
choco=$(manifest_field tool "$1" 7)
|
||||
winget=$(manifest_field tool "$1" 8)
|
||||
brew=$(manifest_field tool "$1" 9)
|
||||
apt=$(manifest_field tool "$1" 10)
|
||||
pacman=$(manifest_field tool "$1" 11)
|
||||
hint=""
|
||||
case "$PLATFORM" in
|
||||
windows)
|
||||
if command -v choco >/dev/null 2>&1 && [ "$choco" != - ]; then
|
||||
hint="$choco (in a terminal opened as administrator)"
|
||||
elif command -v winget >/dev/null 2>&1 && [ "$winget" != - ]; then
|
||||
hint=$winget
|
||||
fi ;;
|
||||
macos)
|
||||
if command -v brew >/dev/null 2>&1 && [ "$brew" != - ]; then hint=$brew; fi ;;
|
||||
*)
|
||||
if command -v apt-get >/dev/null 2>&1 && [ "$apt" != - ]; then
|
||||
hint=$apt
|
||||
elif command -v pacman >/dev/null 2>&1 && [ "$pacman" != - ]; then
|
||||
hint=$pacman
|
||||
fi ;;
|
||||
esac
|
||||
if [ -n "$hint" ]; then
|
||||
printf '%s\n' "$hint"
|
||||
return
|
||||
fi
|
||||
printf 'Install it with the package manager this computer uses, for example:\n'
|
||||
for entry in "choco:$choco" "winget:$winget" "brew:$brew" "apt:$apt" "pacman:$pacman"; do
|
||||
case "$entry" in *:-) continue ;; esac
|
||||
printf ' %s\n' "${entry#*:}"
|
||||
done
|
||||
}
|
||||
|
||||
# --- version helpers ------------------------------------------------------------
|
||||
|
||||
# major.minor of the first version-looking token in $1 ("git version 2.47.1" -> 2.47)
|
||||
version_of() {
|
||||
printf '%s\n' "$1" | sed -n '1{s/^[^0-9]*\([0-9][0-9]*\)\(\.[0-9][0-9]*\)\{0,1\}.*/\1\2/p;}'
|
||||
}
|
||||
|
||||
version_ge() { # <have> <want>, both major[.minor]
|
||||
have=$1 want=$2
|
||||
case "$have" in *.*) ;; *) have="$have.0" ;; esac
|
||||
case "$want" in *.*) ;; *) want="$want.0" ;; esac
|
||||
hm=${have%%.*} hn=${have#*.}
|
||||
wm=${want%%.*} wn=${want#*.}
|
||||
[ "$hm" -gt "$wm" ] || { [ "$hm" -eq "$wm" ] && [ "$hn" -ge "$wn" ]; }
|
||||
}
|
||||
|
||||
# --- finding tools --------------------------------------------------------------
|
||||
|
||||
# The Microsoft Store's app-execution aliases: a python.exe that opens the Store
|
||||
# instead of running anything. Never executed, never recorded.
|
||||
is_store_alias() {
|
||||
case "$1" in *[Ww]indows[Aa]pps/*|*[Ww]indows[Aa]pps\\*) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Every executable called <name> on PATH, in PATH order, store aliases dropped.
|
||||
on_path() {
|
||||
set -f
|
||||
old_ifs=$IFS
|
||||
IFS=:
|
||||
for d in $PATH; do
|
||||
[ -n "$d" ] || d=.
|
||||
for f in "$d/$1" "$d/$1.exe"; do
|
||||
if [ -f "$f" ] && [ -x "$f" ] && ! is_store_alias "$f"; then
|
||||
printf '%s\n' "$f"
|
||||
fi
|
||||
done
|
||||
done
|
||||
IFS=$old_ifs
|
||||
set +f
|
||||
}
|
||||
|
||||
usable() { # <path>
|
||||
[ -n "$1" ] && [ -f "$1" ] && [ -x "$1" ] && ! is_store_alias "$1"
|
||||
}
|
||||
|
||||
# Runs a Python candidate; prints "<major.minor>\n<sys.executable>".
|
||||
probe_python() { # <path> [extra argument, e.g. -3 for py]
|
||||
if [ $# -gt 1 ]; then
|
||||
"$1" "$2" -c "$PYPROBE" 2>/dev/null | tr -d '\r'
|
||||
else
|
||||
"$1" -c "$PYPROBE" 2>/dev/null | tr -d '\r'
|
||||
fi
|
||||
}
|
||||
|
||||
# The value recorded for <name> in .wikitool-tools.json, unescaped.
|
||||
recorded() {
|
||||
[ -f "$TOOLS_FILE" ] || return 1
|
||||
value=$(sed -n "s/^ *\"$1\": *\"\(.*\)\",\{0,1\} *\$/\1/p" "$TOOLS_FILE" | head -n 1)
|
||||
[ -n "$value" ] || return 1
|
||||
printf '%s\n' "$value" | sed 's/\\"/"/g; s/\\\\/\\/g'
|
||||
}
|
||||
|
||||
set_value() { # <name> -> the path given with --set, if any
|
||||
printf '%s\n' "$SETS" | sed -n "s/^$1=//p" | head -n 1
|
||||
}
|
||||
|
||||
# --- --set: every named path has to work, or nothing is written -------------------
|
||||
|
||||
for entry in $(printf '%s\n' "$SETS" | sed -n 's/^\([^=]*\)=.*/\1/p'); do
|
||||
case " $TOOLS " in
|
||||
*" $entry "*) ;;
|
||||
*) echo "preflight: --set names '$entry', which is not a tool this platform needs:$TOOLS" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- python -------------------------------------------------------------------------
|
||||
|
||||
PY="" PY_VERSION="" PY_OLD=""
|
||||
PY_MIN=$(manifest_field tool python 3)
|
||||
|
||||
try_python() { # <path> [extra] -> sets PY/PY_VERSION on success, PY_OLD when too old
|
||||
out=$(probe_python "$@") || return 1
|
||||
version=$(printf '%s\n' "$out" | sed -n 1p)
|
||||
executable=$(printf '%s\n' "$out" | sed -n 2p)
|
||||
[ -n "$version" ] && [ -n "$executable" ] || return 1
|
||||
if version_ge "$version" "$PY_MIN"; then
|
||||
PY=$executable PY_VERSION=$version
|
||||
return 0
|
||||
fi
|
||||
PY_OLD="$version $1"
|
||||
return 1
|
||||
}
|
||||
|
||||
python_extra() { # py and py.exe are the launcher: they need -3
|
||||
case "${1##*/}" in py|py.exe) printf '%s\n' -3 ;; esac
|
||||
}
|
||||
|
||||
given=$(set_value python)
|
||||
if [ -n "$given" ]; then
|
||||
extra=$(python_extra "$given")
|
||||
if ! usable "$given" || ! try_python "$given" $extra; then
|
||||
problem "The path given for Python does not work: $given" \
|
||||
"every wikitool command runs on Python $PY_MIN or newer, and this path did not start one" \
|
||||
"Check the path - it has to be the python executable itself, version $PY_MIN or newer.
|
||||
Then run: $SELF --set python=<full path to python>"
|
||||
BAD_SET=1
|
||||
fi
|
||||
else
|
||||
previous=$(recorded python) || previous=""
|
||||
if [ -n "$previous" ] && usable "$previous"; then
|
||||
try_python "$previous" || true
|
||||
fi
|
||||
if [ -z "$PY" ]; then
|
||||
if [ "$PLATFORM" = windows ]; then
|
||||
order="python: py:-3 python3:"
|
||||
else
|
||||
order="python3: python:"
|
||||
fi
|
||||
for candidate in $order; do
|
||||
name=${candidate%%:*}
|
||||
extra=${candidate#*:}
|
||||
found=$(on_path "$name")
|
||||
[ -n "$found" ] || continue
|
||||
# A here-document, not a pipe: `break 2` has to leave the outer loop
|
||||
# of this shell, and a path may contain spaces ("Program Files").
|
||||
while IFS= read -r path; do
|
||||
if [ -n "$extra" ]; then
|
||||
try_python "$path" "$extra" && break 2
|
||||
else
|
||||
try_python "$path" && break 2
|
||||
fi
|
||||
done <<EOF
|
||||
$found
|
||||
EOF
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- the other tools --------------------------------------------------------------
|
||||
|
||||
REPORT=""
|
||||
report() { # <status> <name> <version> <path>
|
||||
REPORT="$REPORT$(printf '%-8s %-7s %-8s %s' "$1" "$2" "$3" "$4")
|
||||
"
|
||||
}
|
||||
|
||||
if [ -n "$PY" ]; then
|
||||
report OK python "$PY_VERSION" "$PY"
|
||||
eval "FOUND_python=\$PY"
|
||||
elif [ "$BAD_SET" -eq 0 ]; then
|
||||
label=$(manifest_field tool python 5)
|
||||
why=$(manifest_field tool python 6)
|
||||
if [ -n "$PY_OLD" ]; then
|
||||
report TOO_OLD python "${PY_OLD%% *}" "${PY_OLD#* }"
|
||||
problem "$label ${PY_OLD%% *} is too old - this stack needs $PY_MIN or newer." "$why" \
|
||||
"$(install_hint python)
|
||||
Or, if a newer one is already installed, give its full path:
|
||||
$SELF --set python=<full path to python>"
|
||||
else
|
||||
report MISSING python - -
|
||||
problem "$label $PY_MIN or newer was not found." "$why" \
|
||||
"$(install_hint python)
|
||||
Or, if it is installed somewhere this check did not look, give its full path:
|
||||
$SELF --set python=<full path to python>"
|
||||
fi
|
||||
fi
|
||||
|
||||
for tool in $TOOLS; do
|
||||
[ "$tool" = python ] && continue
|
||||
label=$(manifest_field tool "$tool" 5)
|
||||
why=$(manifest_field tool "$tool" 6)
|
||||
minimum=$(manifest_field tool "$tool" 3)
|
||||
given=$(set_value "$tool")
|
||||
path=""
|
||||
if [ -n "$given" ]; then
|
||||
if usable "$given"; then
|
||||
path=$given
|
||||
else
|
||||
problem "The path given for $label does not work: $given" "$why" \
|
||||
"Check the path - it has to be the $tool executable itself.
|
||||
Then run: $SELF --set $tool=<full path to $tool>"
|
||||
BAD_SET=1
|
||||
continue
|
||||
fi
|
||||
else
|
||||
previous=$(recorded "$tool") || previous=""
|
||||
if [ -n "$previous" ] && usable "$previous"; then
|
||||
path=$previous
|
||||
else
|
||||
path=$(on_path "$tool" | head -n 1)
|
||||
fi
|
||||
fi
|
||||
if [ -z "$path" ]; then
|
||||
report MISSING "$tool" - -
|
||||
problem "$label was not found." "$why" \
|
||||
"$(install_hint "$tool")
|
||||
Or, if it is installed somewhere this check did not look, give its full path:
|
||||
$SELF --set $tool=<full path to $tool>"
|
||||
continue
|
||||
fi
|
||||
version=$(version_of "$("$path" --version 2>/dev/null | tr -d '\r')")
|
||||
if [ "$minimum" != - ] && { [ -z "$version" ] || ! version_ge "$version" "$minimum"; }; then
|
||||
report TOO_OLD "$tool" "${version:-unknown}" "$path"
|
||||
problem "$label ${version:-of unknown version} is too old - this stack needs $minimum or newer." "$why" \
|
||||
"$(install_hint "$tool")"
|
||||
continue
|
||||
fi
|
||||
native=$(native_path "$path")
|
||||
report OK "$tool" "${version:--}" "$native"
|
||||
eval "FOUND_$tool=\$native"
|
||||
done
|
||||
|
||||
# --- install folder length (Windows, long paths off) ------------------------------
|
||||
|
||||
longpaths_enabled() {
|
||||
if [ -n "${CHEMENU_PREFLIGHT_LONGPATHS:-}" ]; then
|
||||
[ "$CHEMENU_PREFLIGHT_LONGPATHS" = 1 ]
|
||||
return
|
||||
fi
|
||||
# MSYS would rewrite the `/v` into a path without the exclusion. An unreadable
|
||||
# key counts as "off": the limit then protects a machine it did not have to.
|
||||
answer=$(MSYS2_ARG_CONV_EXCL='*' reg query 'HKLM\SYSTEM\CurrentControlSet\Control\FileSystem' \
|
||||
/v LongPathsEnabled 2>/dev/null) || return 1
|
||||
case "$answer" in *0x1*) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Length in UTF-16 code units, which is what MAX_PATH counts.
|
||||
utf16_length() {
|
||||
if command -v iconv >/dev/null 2>&1; then
|
||||
bytes=$(printf '%s' "$1" | iconv -f UTF-8 -t UTF-16LE 2>/dev/null | wc -c | tr -d ' ')
|
||||
if [ "${bytes:-0}" -gt 0 ]; then
|
||||
echo $((bytes / 2))
|
||||
return
|
||||
fi
|
||||
fi
|
||||
echo ${#1}
|
||||
}
|
||||
|
||||
if [ "$PLATFORM" = windows ] && ! longpaths_enabled; then
|
||||
limit=$(manifest_field limit install_dir_max 3)
|
||||
folder=$(native_path "$ROOT")
|
||||
length=$(utf16_length "$folder")
|
||||
if [ "$length" -gt "$limit" ]; then
|
||||
problem "The folder this wiki is installed in is too long ($length characters, at most $limit): $folder" \
|
||||
"Windows on this computer only allows paths of up to 259 characters, and the wiki's own files need the rest" \
|
||||
"Move the wiki to a shorter folder, for example C:\\Chemenu, and run this check there.
|
||||
Alternatively, someone with administrator rights can turn on long paths in Windows."
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- record what was found ----------------------------------------------------------
|
||||
|
||||
json_string() {
|
||||
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
}
|
||||
|
||||
write_tools_file() { # <true|false>
|
||||
tmp="$TOOLS_FILE.tmp.$$"
|
||||
{
|
||||
printf '{\n "schema": 1,\n "complete": %s,\n "tools": {' "$1"
|
||||
separator=""
|
||||
for tool in $TOOLS; do
|
||||
eval "value=\${FOUND_$tool:-}"
|
||||
[ -n "$value" ] || continue
|
||||
printf '%s\n "%s": "%s"' "$separator" "$tool" "$(json_string "$value")"
|
||||
separator=","
|
||||
done
|
||||
printf '\n }\n}\n'
|
||||
} > "$tmp" && mv -f "$tmp" "$TOOLS_FILE"
|
||||
}
|
||||
|
||||
printf '%s' "$REPORT"
|
||||
|
||||
if [ "$BAD_SET" -eq 1 ]; then
|
||||
stop # nothing is written for a path that does not work
|
||||
fi
|
||||
write_tools_file false
|
||||
if [ "$PROBLEMS" -gt 0 ]; then
|
||||
stop
|
||||
fi
|
||||
|
||||
# --- tools/.venv ----------------------------------------------------------------------
|
||||
|
||||
venv_python() {
|
||||
if [ -x "$VENV/bin/python" ]; then
|
||||
printf '%s\n' "$VENV/bin/python"
|
||||
elif [ -f "$VENV/Scripts/python.exe" ]; then
|
||||
printf '%s\n' "$VENV/Scripts/python.exe"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
last_lines() {
|
||||
printf '%s\n' "$1" | tail -n 5 | sed 's/^/ /'
|
||||
}
|
||||
|
||||
VPY=$(venv_python) || VPY=""
|
||||
if [ -z "$VPY" ] || ! "$VPY" -m pip --version >/dev/null 2>&1; then
|
||||
if ! output=$("$PY" -m venv --clear "$VENV" 2>&1) || ! VPY=$(venv_python); then
|
||||
fix="Python said:
|
||||
$(last_lines "$output")"
|
||||
if [ "$PLATFORM" = linux ] && command -v apt-get >/dev/null 2>&1; then
|
||||
fix="sudo apt install python3-venv
|
||||
$fix"
|
||||
fi
|
||||
problem "The wiki's own Python environment (tools/.venv) could not be created." \
|
||||
"wikitool runs in that environment, so that nothing it installs touches the rest of the computer" \
|
||||
"$fix"
|
||||
stop
|
||||
fi
|
||||
fi
|
||||
|
||||
want=$("$VPY" -c 'import hashlib, sys; print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())' \
|
||||
"$REQUIREMENTS" 2>/dev/null | tr -d '\r')
|
||||
have=$(cat "$STAMP" 2>/dev/null) || have=""
|
||||
if [ -z "$want" ] || [ "$want" != "$have" ]; then
|
||||
if ! output=$("$VPY" -m pip install --disable-pip-version-check --quiet -r "$REQUIREMENTS" 2>&1); then
|
||||
problem "The libraries wikitool needs could not be installed into tools/.venv." \
|
||||
"they are downloaded once from the internet; without them no wikitool command starts" \
|
||||
"Check that this computer can reach the internet (a proxy or a security program can block it; if so, ask whoever looks after this computer).
|
||||
pip said:
|
||||
$(last_lines "$output")"
|
||||
stop
|
||||
fi
|
||||
printf '%s\n' "$want" > "$STAMP"
|
||||
fi
|
||||
echo "OK venv - $(native_path "$VENV")"
|
||||
|
||||
write_tools_file true
|
||||
echo ""
|
||||
echo "Preflight passed. Tool paths are recorded in .wikitool-tools.json; tools/wikitool is ready."
|
||||
exit 0
|
||||
Reference in new issue
Block a user