From d823e11f19dc6fbac0690cde5c48eb44967cc857 Mon Sep 17 00:00:00 2001 From: Aleksandr Meshchriakov Date: Tue, 28 Apr 2026 14:22:19 +0200 Subject: [PATCH] ci: make python setup resilient --- scripts/ensure-ci-python.sh | 90 +++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/scripts/ensure-ci-python.sh b/scripts/ensure-ci-python.sh index 161b525..d63c9b1 100755 --- a/scripts/ensure-ci-python.sh +++ b/scripts/ensure-ci-python.sh @@ -11,11 +11,53 @@ else PYTHON_BIN="python${PYTHON_MAJOR}" fi +python_matches() { + local candidate="$1" + [[ -x "${candidate}" ]] || return 1 + "${candidate}" - "${PYTHON_MAJOR}" "${PYTHON_MINOR:-}" <<'PY' +import sys + +expected_major = int(sys.argv[1]) +expected_minor = sys.argv[2] +if sys.version_info.major != expected_major: + raise SystemExit(1) +if expected_minor and sys.version_info.minor != int(expected_minor): + raise SystemExit(1) +PY +} + +python_has_venv() { + local candidate="$1" + "${candidate}" -m venv --help >/dev/null 2>&1 +} + +print_if_usable() { + local candidate="$1" + if python_matches "${candidate}" && python_has_venv "${candidate}"; then + printf '%s\n' "${candidate}" + exit 0 + fi +} + if command -v "${PYTHON_BIN}" >/dev/null 2>&1; then - command -v "${PYTHON_BIN}" - exit 0 + print_if_usable "$(command -v "${PYTHON_BIN}")" fi +TOOLCACHE_DIR="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" +HOME_DIR="${HOME:-/root}" + +shopt -s nullglob +# shellcheck disable=SC2140 +for candidate in \ + "${TOOLCACHE_DIR}"/Python/"${PYTHON_MAJOR}.${PYTHON_MINOR}"*/x64/bin/python \ + "${TOOLCACHE_DIR}"/Python/"${PYTHON_MAJOR}.${PYTHON_MINOR}"*/x64/bin/python3 \ + "${TOOLCACHE_DIR}"/uv-python/cpython-"${PYTHON_MAJOR}.${PYTHON_MINOR}"*/bin/python \ + "${HOME_DIR}"/.local/share/uv/python/cpython-"${PYTHON_MAJOR}.${PYTHON_MINOR}"*/bin/python \ + "${HOME_DIR}"/.cache/uv/python/cpython-"${PYTHON_MAJOR}.${PYTHON_MINOR}"*/bin/python; do + print_if_usable "${candidate}" +done +shopt -u nullglob + APT_RUNNER=() if [[ "$(id -u)" -ne 0 ]]; then APT_RUNNER=(sudo) @@ -23,12 +65,42 @@ fi export DEBIAN_FRONTEND=noninteractive -"${APT_RUNNER[@]}" apt-get update >&2 -if ! "${APT_RUNNER[@]}" apt-get install -y "${PYTHON_BIN}" "${PYTHON_BIN}-venv" >&2; then - "${APT_RUNNER[@]}" apt-get install -y software-properties-common >&2 - "${APT_RUNNER[@]}" add-apt-repository -y ppa:deadsnakes/ppa >&2 - "${APT_RUNNER[@]}" apt-get update >&2 - "${APT_RUNNER[@]}" apt-get install -y "${PYTHON_BIN}" "${PYTHON_BIN}-venv" >&2 +if "${APT_RUNNER[@]}" apt-get update >&2 \ + && "${APT_RUNNER[@]}" apt-get install -y "${PYTHON_BIN}" "${PYTHON_BIN}-venv" >&2; then + print_if_usable "$(command -v "${PYTHON_BIN}")" fi -command -v "${PYTHON_BIN}" +install_uv_standalone() { + if command -v uv >/dev/null 2>&1; then + command -v uv + return 0 + fi + + local uv_dir + uv_dir="$(mktemp -d)" + local install_url="https://astral.sh/uv/${UV_VERSION:-0.7.2}/install.sh" + if ! curl -LsSf "${install_url}" | env UV_INSTALL_DIR="${uv_dir}/bin" sh >&2; then + curl -LsSf "https://astral.sh/uv/install.sh" | env UV_INSTALL_DIR="${uv_dir}/bin" sh >&2 + fi + printf '%s\n' "${uv_dir}/bin/uv" +} + +UV_BIN="$(install_uv_standalone)" +UV_PYTHON_INSTALL_DIR="${TOOLCACHE_DIR:-${HOME_DIR}/.cache}/uv-python" \ + "${UV_BIN}" python install "${TARGET_VERSION}" --no-progress >&2 + +UV_PYTHON_CANDIDATE="$( + UV_PYTHON_INSTALL_DIR="${TOOLCACHE_DIR:-${HOME_DIR}/.cache}/uv-python" \ + "${UV_BIN}" python find "${TARGET_VERSION}" --managed-python +)" +print_if_usable "${UV_PYTHON_CANDIDATE}" + +if "${APT_RUNNER[@]}" apt-get install -y software-properties-common >&2 \ + && "${APT_RUNNER[@]}" add-apt-repository -y ppa:deadsnakes/ppa >&2 \ + && "${APT_RUNNER[@]}" apt-get update >&2 \ + && "${APT_RUNNER[@]}" apt-get install -y "${PYTHON_BIN}" "${PYTHON_BIN}-venv" >&2; then + print_if_usable "$(command -v "${PYTHON_BIN}")" +fi + +printf 'Unable to install a usable Python %s with venv support\n' "${TARGET_VERSION}" >&2 +exit 1