mql5/Scripts/replicate_venv.sh

202 lines
6 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
set -euo pipefail
# replicate_venv.sh
# Export and recreate a Python virtual environment across machines.
#
# Quick start:
# 1) On source machine:
# ./Scripts/replicate_venv.sh export --venv /path/to/.venv --out .venv-lock
# 2) Copy .venv-lock and this script to target machine.
# 3) On target machine:
# ./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3
#
# The lock folder contains:
# - requirements.txt pip freeze output (all Python libraries)
# - python-version.txt full Python version from source venv
# - platform.txt source OS/platform info
# - bootstrap.txt recreation command and dependency notes
usage() {
cat <<'EOF'
Usage:
replicate_venv.sh export --venv <source_venv_path> [--out <lock_dir>]
replicate_venv.sh recreate --venv <target_venv_path> [--from <lock_dir>] [--python <python_cmd>]
Options:
--venv Path to source/target virtual environment directory.
--out Export destination directory (default: .venv-lock).
--from Input lock directory (default: .venv-lock).
--python Python command to create new venv (default: python3).
-h, --help Show this help.
Quick Start:
Source machine:
./Scripts/replicate_venv.sh export --venv /path/to/.venv --out .venv-lock
Target machine:
./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3
Dependencies required on target machine:
1. Python 3 with venv support.
2. Internet access to install pip packages from requirements.txt (or access to your package mirror).
3. Build tools for packages that compile native extensions (when needed by your requirements).
Notes:
- This script recreates Python package dependencies; it does not install system packages for you.
- It will warn if source and target Python versions differ.
EOF
}
abort() {
echo "Error: $*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || abort "Required command not found: $1"
}
ensure_python_has_venv() {
local python_cmd="$1"
"$python_cmd" -c 'import venv' >/dev/null 2>&1 || abort "Python does not include the venv module. Install Python with venv support and retry."
}
ensure_python_has_pip_bootstrap() {
local python_cmd="$1"
"$python_cmd" -m ensurepip --upgrade >/dev/null 2>&1 || true
}
find_venv_python() {
local venv_path="$1"
if [[ -x "$venv_path/bin/python" ]]; then
echo "$venv_path/bin/python"
elif [[ -x "$venv_path/Scripts/python.exe" ]]; then
# Windows-style venv path support.
echo "$venv_path/Scripts/python.exe"
else
abort "Could not find Python inside virtualenv at: $venv_path"
fi
}
mode="${1:-}"
[[ -n "$mode" ]] || { usage; exit 1; }
shift || true
case "$mode" in
export)
venv_path=""
out_dir=".venv-lock"
while [[ $# -gt 0 ]]; do
case "$1" in
--venv)
venv_path="${2:-}"
shift 2
;;
--out)
out_dir="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
abort "Unknown option for export: $1"
;;
esac
done
[[ -n "$venv_path" ]] || abort "Missing required --venv for export"
[[ -d "$venv_path" ]] || abort "Venv path does not exist: $venv_path"
py_bin="$(find_venv_python "$venv_path")"
mkdir -p "$out_dir"
"$py_bin" -m pip freeze > "$out_dir/requirements.txt"
"$py_bin" -c 'import platform; print(platform.python_version())' > "$out_dir/python-version.txt"
"$py_bin" -c 'import platform; print(platform.platform())' > "$out_dir/platform.txt"
cat > "$out_dir/bootstrap.txt" <<'EOF'
Recreate command:
./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3
Dependencies needed on target machine:
- Python 3 with venv module enabled
- Network access (or internal package mirror) for pip installs
- Build toolchain for any packages requiring native compilation
If Python differs from source version, recreate may still work but package compatibility can vary.
EOF
echo "Export complete."
echo "Lock dir: $out_dir"
echo "Files: $out_dir/requirements.txt, $out_dir/python-version.txt, $out_dir/platform.txt, $out_dir/bootstrap.txt"
;;
recreate)
target_venv=""
from_dir=".venv-lock"
python_cmd="python3"
while [[ $# -gt 0 ]]; do
case "$1" in
--venv)
target_venv="${2:-}"
shift 2
;;
--from)
from_dir="${2:-}"
shift 2
;;
--python)
python_cmd="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
abort "Unknown option for recreate: $1"
;;
esac
done
[[ -n "$target_venv" ]] || abort "Missing required --venv for recreate"
[[ -f "$from_dir/requirements.txt" ]] || abort "Missing lock file: $from_dir/requirements.txt"
require_cmd "$python_cmd"
ensure_python_has_venv "$python_cmd"
ensure_python_has_pip_bootstrap "$python_cmd"
if [[ -d "$target_venv" ]]; then
abort "Target venv already exists at $target_venv. Remove it first or use a different path."
fi
"$python_cmd" -m venv "$target_venv"
py_bin="$(find_venv_python "$target_venv")"
"$py_bin" -m pip --version >/dev/null 2>&1 || abort "pip is unavailable in the target venv. Ensure your Python installation supports ensurepip."
"$py_bin" -m pip install --upgrade pip setuptools wheel
"$py_bin" -m pip install -r "$from_dir/requirements.txt"
if [[ -f "$from_dir/python-version.txt" ]]; then
source_ver="$(cat "$from_dir/python-version.txt")"
target_ver="$($py_bin -c 'import platform; print(platform.python_version())')"
if [[ "$source_ver" != "$target_ver" ]]; then
echo "Warning: Source Python version ($source_ver) differs from target ($target_ver)."
fi
fi
echo "Recreate complete at: $target_venv"
;;
-h|--help)
usage
;;
*)
abort "Unknown mode: $mode"
;;
esac