30f6f3758f
Permite distribuir graph_explorer.exe Windows sin dependencia de WSL
ni del .venv del registry. Tambien funciona en Linux como bundle
autocontenido portable.
Cambios:
1. tools/freeze_python_runtime.sh
- Linux: copia python-build-standalone (uv) ~87 MB,
elimina marker EXTERNALLY-MANAGED, instala wheels.
- Windows: descarga python-3.12.7-embed-amd64.zip oficial
(~12 MB), habilita site-packages, instala wheels via
pip install --target --platform win_amd64.
- Idempotente via runtime/.lock con SHA256 del estado.
- Lee python_runtime_deps del frontmatter de app.md.
2. jobs.cpp::cached_python_runtime() — resolver con cadena:
1. <exe_dir>/runtime/python/{python.exe|bin/python3} (embedded)
2. $FN_PYTHON (env)
3. <registry_root>/python/.venv/bin/python3 (registry_venv)
4. python3 del PATH (system)
Loggea procedencia al iniciar jobs_init.
3. POSIX run_subprocess: usa el runtime resuelto en lugar del
path hardcodeado.
4. Windows run_subprocess: ramifica por needs_wsl. Si embedded
o env, lanza Python Windows nativo via CreateProcessW
directamente (run_path tambien Windows nativo). Solo el
legacy registry_venv sigue por wsl.exe.
5. app.md: nuevos campos python_runtime: true y
python_runtime_deps: [requests, certifi, urllib3].
6. .gitignore extendido con runtime/, projects/, _vendored/,
.vendor.lock, binarios Go de enrichers.
Tests: 26/26 verde — 16 originales + 6 dispatcher fase A + 4
nuevos del resolver fase B (con/sin embed, FN_PYTHON, idempotencia
del freeze script).
Smoke E2E manual: runtime/python/bin/python3 ejecuta web_search
con cwd /tmp y registry_root pasado en ctx, sin tocar el .venv del
registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
6.4 KiB
Bash
Executable File
168 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# freeze_python_runtime.sh — genera <app_dir>/runtime/python/ embebido
|
|
# para distribuir graph_explorer (u otra app) sin dependencia de WSL
|
|
# ni del .venv del registry.
|
|
#
|
|
# Issue 0033 fase B.
|
|
#
|
|
# Uso:
|
|
# tools/freeze_python_runtime.sh <app_dir> <platform>
|
|
# <platform> = linux | windows
|
|
#
|
|
# Lee `python_runtime_deps` del frontmatter de <app_dir>/app.md
|
|
# (lista YAML inline). Tambien acepta override via env var:
|
|
# PY_DEPS="requests certifi urllib3" tools/freeze_python_runtime.sh ...
|
|
#
|
|
# Idempotente — calcula un hash de (PY_VERSION + deps + platform) y
|
|
# lo guarda en runtime/.lock. Si coincide con el actual, no rehace.
|
|
#
|
|
# Salida (Windows):
|
|
# <app_dir>/runtime/python/python.exe
|
|
# <app_dir>/runtime/python/Lib/site-packages/<deps>/...
|
|
#
|
|
# Salida (Linux):
|
|
# <app_dir>/runtime/python/bin/python3
|
|
# <app_dir>/runtime/python/lib/...
|
|
|
|
set -euo pipefail
|
|
|
|
PY_VERSION="${PY_VERSION:-3.12.7}"
|
|
APP_DIR="${1:?app_dir requerido}"
|
|
PLATFORM="${2:?platform requerido (linux|windows)}"
|
|
|
|
if [[ ! -d "$APP_DIR" ]]; then
|
|
echo "ERROR: $APP_DIR no es un directorio" >&2
|
|
exit 1
|
|
fi
|
|
|
|
RUNTIME_DIR="$APP_DIR/runtime/python"
|
|
APP_MD="$APP_DIR/app.md"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Resolver dependencias: PY_DEPS (env) > frontmatter de app.md > vacio.
|
|
# ----------------------------------------------------------------------------
|
|
deps_from_env="${PY_DEPS:-}"
|
|
deps_from_md=""
|
|
|
|
if [[ -z "$deps_from_env" && -f "$APP_MD" ]]; then
|
|
# Parser ad-hoc del frontmatter (entre el primer y segundo `---`).
|
|
# Busca lineas que empiezan por ` - ` despues de una linea
|
|
# `python_runtime_deps:`. Suficiente para el formato YAML simple
|
|
# que usamos en los app.md del registry. Si necesitamos algo mas
|
|
# complejo (anidados, comentarios), portarlo a Python o usar yq.
|
|
deps_from_md=$(awk '
|
|
/^---$/ { fm = !fm; next }
|
|
!fm { next }
|
|
/^python_runtime_deps:[[:space:]]*$/ { collecting = 1; next }
|
|
collecting && /^[[:space:]]*-[[:space:]]+/ {
|
|
sub(/^[[:space:]]*-[[:space:]]+/, "")
|
|
sub(/[[:space:]]*#.*$/, "")
|
|
gsub(/[\047"]/, "")
|
|
print
|
|
next
|
|
}
|
|
collecting && /^[^[:space:]-]/ { collecting = 0 }
|
|
' "$APP_MD" | xargs)
|
|
fi
|
|
|
|
DEPS="${deps_from_env:-$deps_from_md}"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Hash del estado: si coincide con runtime/.lock no rehacemos nada.
|
|
# ----------------------------------------------------------------------------
|
|
state_hash=$(echo -n "$PY_VERSION|$PLATFORM|$DEPS" | sha256sum | cut -d' ' -f1)
|
|
lock_file="$APP_DIR/runtime/.lock"
|
|
if [[ -f "$lock_file" ]]; then
|
|
cur=$(cat "$lock_file" 2>/dev/null || echo "")
|
|
if [[ "$cur" == "$state_hash" ]]; then
|
|
echo "freeze: sin cambios (.lock = $state_hash)"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Limpieza y creacion.
|
|
# ----------------------------------------------------------------------------
|
|
echo "freeze: $PLATFORM, deps=[$DEPS], py=$PY_VERSION"
|
|
rm -rf "$RUNTIME_DIR"
|
|
mkdir -p "$RUNTIME_DIR"
|
|
|
|
if [[ "$PLATFORM" == "windows" ]]; then
|
|
# Windows embedded distribution (zip oficial python.org).
|
|
zip="python-${PY_VERSION}-embed-amd64.zip"
|
|
cache="${TMPDIR:-/tmp}/$zip"
|
|
if [[ ! -f "$cache" ]]; then
|
|
echo "freeze: descargando $zip..."
|
|
curl -sSL --fail \
|
|
"https://www.python.org/ftp/python/$PY_VERSION/$zip" \
|
|
-o "$cache.tmp"
|
|
mv "$cache.tmp" "$cache"
|
|
fi
|
|
unzip -oq "$cache" -d "$RUNTIME_DIR"
|
|
|
|
# Habilitar site-packages (el embedded viene con ._pth restrictivo).
|
|
short=$(echo "$PY_VERSION" | awk -F. '{print $1$2}')
|
|
pth="$RUNTIME_DIR/python${short}._pth"
|
|
if [[ -f "$pth" ]]; then
|
|
sed -i 's|^#import site|import site|' "$pth"
|
|
fi
|
|
|
|
# Instalar deps con pip "host" usando el embedded como target.
|
|
if [[ -n "$DEPS" ]]; then
|
|
echo "freeze: pip install $DEPS (target=$RUNTIME_DIR/Lib/site-packages)"
|
|
# `--platform win_amd64 --only-binary=:all:` fuerza wheels
|
|
# binarios para Windows aunque pip corra en Linux.
|
|
python3 -m pip install --quiet \
|
|
--target "$RUNTIME_DIR/Lib/site-packages" \
|
|
--platform win_amd64 --only-binary=:all: \
|
|
--python-version "$PY_VERSION" \
|
|
$DEPS
|
|
fi
|
|
|
|
elif [[ "$PLATFORM" == "linux" ]]; then
|
|
# En Linux preferimos `uv` si esta disponible — descarga un
|
|
# Python standalone (de python-build-standalone) que no depende
|
|
# del Python del sistema y empaqueta todo. Si no, fallback a
|
|
# `python3 -m venv` (requiere python3-venv del sistema).
|
|
if command -v uv >/dev/null 2>&1; then
|
|
# uv mantiene un cache global de Pythons standalone (de
|
|
# python-build-standalone) en ~/.local/share/uv/python/. Para
|
|
# un runtime distribuible copiamos ese arbol completo (~82 MB)
|
|
# y luego instalamos deps con su pip propio. Sin esto el venv
|
|
# quedaria con symlinks al cache de uv que se rompen al
|
|
# mover la carpeta a otra maquina.
|
|
uv python install "$PY_VERSION" >/dev/null 2>&1 || true
|
|
py_root=$(uv python find "$PY_VERSION" 2>/dev/null | xargs -I{} dirname {} | xargs dirname)
|
|
if [[ -z "$py_root" || ! -d "$py_root" ]]; then
|
|
echo "ERROR: no se localizo Python $PY_VERSION via uv" >&2
|
|
exit 4
|
|
fi
|
|
cp -r "$py_root/." "$RUNTIME_DIR/"
|
|
# python-build-standalone deja un marker EXTERNALLY-MANAGED
|
|
# (PEP 668) que bloquea pip install. Es nuestro runtime, no
|
|
# gestion del sistema — lo eliminamos.
|
|
find "$RUNTIME_DIR" -name "EXTERNALLY-MANAGED" -delete 2>/dev/null || true
|
|
py_bin="$RUNTIME_DIR/bin/python3"
|
|
if [[ -n "$DEPS" ]]; then
|
|
echo "freeze: pip install $DEPS"
|
|
"$py_bin" -m pip install --quiet --no-warn-script-location $DEPS
|
|
fi
|
|
else
|
|
python3 -m venv "$RUNTIME_DIR" --copies >/dev/null
|
|
if [[ ! -x "$RUNTIME_DIR/bin/pip" ]]; then
|
|
echo "ERROR: pip no disponible. Instala uv o python3-venv del sistema." >&2
|
|
exit 3
|
|
fi
|
|
if [[ -n "$DEPS" ]]; then
|
|
echo "freeze: pip install $DEPS"
|
|
"$RUNTIME_DIR/bin/pip" install --quiet $DEPS
|
|
fi
|
|
fi
|
|
else
|
|
echo "ERROR: platform desconocida: $PLATFORM (esperado linux|windows)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
echo "$state_hash" > "$lock_file"
|
|
echo "freeze: OK ($RUNTIME_DIR)"
|