diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6265beb0a..86df3ed93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1375,14 +1375,15 @@ jobs: env: UV_TEST_VERSION: ${{ matrix.uv }} run: | - python -m pip install --disable-pip-version-check --upgrade pip "uv==$UV_TEST_VERSION" + scripts/pip-install-retry.sh --upgrade pip "uv==$UV_TEST_VERSION" uv --version - name: Install uv (PDM / Hatch legs) if: matrix.pdm != '' || matrix.hatch != '' # The suites bootstrap the pinned PDM / Hatch into their own venv # with uv; this uv is tooling, not the release under test. - run: python -m pip install uv==0.11.19 + shell: bash + run: scripts/pip-install-retry.sh uv==0.11.19 - name: Install pinned Poetry if: matrix.poetry != '' @@ -1394,7 +1395,7 @@ jobs: env: POETRY_TEST_VERSION: ${{ matrix.poetry }} run: | - python -m pip install uv==0.11.19 + scripts/pip-install-retry.sh uv==0.11.19 case "$POETRY_TEST_VERSION" in 1.0.*|1.1.*) py=3.8.20 ;; *) py=3.12 ;; esac uv venv "$RUNNER_TEMP/poetry" --python "$py" extra=""; [ "$POETRY_TEST_VERSION" = "1.2.2" ] && extra="cleo==1.0.0a5" @@ -2479,7 +2480,7 @@ jobs: - name: Install uv if: steps.gate.outputs.run == 'true' - run: python -m pip install --disable-pip-version-check uv && uv --version + run: scripts/pip-install-retry.sh uv && uv --version - name: Setup Ruby if: steps.gate.outputs.run == 'true' diff --git a/.github/workflows/pdm-compatibility.yml b/.github/workflows/pdm-compatibility.yml index 6b194dec4..6d1bfa165 100644 --- a/.github/workflows/pdm-compatibility.yml +++ b/.github/workflows/pdm-compatibility.yml @@ -129,7 +129,7 @@ jobs: python-version: '3.12' # uv bootstraps every pinned PDM release (and its interpreter) itself; # pinning uv keeps the bootstrap reproducible across runner images. - - run: python -m pip install uv==0.11.19 + - run: scripts/pip-install-retry.sh uv==0.11.19 - name: Exercise the native PDM installers shell: bash env: @@ -226,7 +226,7 @@ jobs: - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: '3.12' - - run: python -m pip install uv==0.11.19 + - run: scripts/pip-install-retry.sh uv==0.11.19 - name: Real-PDM hosted + vendored flows with manifest-less VEX env: SOCKET_PATCH_PDM_E2E_REQUIRED: '1' diff --git a/.github/workflows/poetry-compatibility.yml b/.github/workflows/poetry-compatibility.yml index 139c68513..94c6dc233 100644 --- a/.github/workflows/poetry-compatibility.yml +++ b/.github/workflows/poetry-compatibility.yml @@ -110,7 +110,7 @@ jobs: uses: ./.github/actions/pin-socket-hosts # uv bootstraps every pinned Poetry release (and its interpreter) # itself; pinning uv keeps the bootstrap reproducible. - - run: python -m pip install uv==0.11.19 + - run: scripts/pip-install-retry.sh uv==0.11.19 - name: Exercise the native Poetry installers shell: bash env: diff --git a/scripts/pip-install-retry.sh b/scripts/pip-install-retry.sh new file mode 100755 index 000000000..7809b3861 --- /dev/null +++ b/scripts/pip-install-retry.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# `python -m pip install` that survives a PyPI download blip. +# +# scripts/pip-install-retry.sh +# +# pip's own `--retries` covers failed connections, not a body cut off +# mid-download: an `IncompleteRead` from files.pythonhosted.org aborts the +# install at once ("Could not install packages due to an OSError: +# Connection broken: IncompleteRead"). One such cut evicted a merge-queue +# entry in hosted-e2e, so the CI tool installs go through here: up to 4 +# attempts with a growing pause. The args are passed through unchanged, so +# a pinned `pkg==X.Y.Z` stays pinned. +set -euo pipefail + +if [ "$#" -eq 0 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +attempts=4 +for attempt in $(seq 1 "$attempts"); do + if python -m pip install --disable-pip-version-check "$@"; then + exit 0 + fi + if [ "$attempt" = "$attempts" ]; then + echo "::error::pip install $* failed on all $attempts attempts" + exit 1 + fi + echo "::warning::pip install $* attempt $attempt failed; retrying" + sleep $((attempt * 10)) +done