202 lines
6 KiB
Bash
202 lines
6 KiB
Bash
#!/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
|