From eb1c6c26ffa19179201ffdac00897c7eba112191 Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Wed, 19 Aug 2026 11:20:17 -0400 Subject: [PATCH 1/2] ci_scripts/check_versions.py: gracefully handle existence of upgrade PRs The workflow currently fails every night because of a backlog of version upgrade PRs already being open from previous runs. Improve check_versions.py script so that if it can't open these PRs for versions RISE doesn't yet build, then it finishes gracefully. Do this by trying to confirm the existence of a PR for the given version(s), then warning the maintainer that they should check for them manually if they can't be detected automatically. Additionally, add three retries when attempting to retrieve package information from PyPI, with a backoff time, so that the check doesn't fail if information isn't successfully retrieved for one or two packages during the run. AI-Generated: Uses Claude Sonnet 5 Signed-off-by: Trevor Gamblin --- ci_scripts/check_versions.py | 125 +++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/ci_scripts/check_versions.py b/ci_scripts/check_versions.py index f67cf8c6..3ffd42d9 100755 --- a/ci_scripts/check_versions.py +++ b/ci_scripts/check_versions.py @@ -17,6 +17,7 @@ import subprocess import sys import os +import time from typing import Dict, List, Optional from packaging import version from pathlib import Path @@ -49,40 +50,49 @@ def read_packages() -> List[str]: return packages -def get_registry_latest_version(package: str) -> Optional[str]: - """Get the latest version available in the riscv64 registry.""" - try: - result = subprocess.run([ - "pip", "index", "versions", package, - "--index-url", REGISTRY_URL, - "--platform", "manylinux_2_34_riscv64", - "--platform", "manylinux_2_35_riscv64", - "--platform", "manylinux_2_39_riscv64", - "--python-version", "3.12" - ], capture_output=True, text=True, timeout=30) - - if result.returncode != 0: +def get_registry_latest_version(package: str, retries: int = 3) -> Optional[str]: + """Get the latest version available in the riscv64 registry, retrying on transient failures.""" + for attempt in range(retries): + try: + result = subprocess.run([ + "pip", "index", "versions", package, + "--index-url", REGISTRY_URL, + "--platform", "manylinux_2_34_riscv64", + "--platform", "manylinux_2_35_riscv64", + "--platform", "manylinux_2_39_riscv64", + "--python-version", "3.12" + ], capture_output=True, text=True, timeout=30) + + if result.returncode != 0: + if attempt < retries - 1: + time.sleep(2 * (attempt + 1)) + continue + return None + + for line in result.stdout.split('\n'): + if "Available versions:" in line: + versions_part = line.split("Available versions:")[1].strip() + if versions_part: + versions = [v.strip() for v in versions_part.split(',')] + return versions[0] if versions else None return None - - for line in result.stdout.split('\n'): - if "Available versions:" in line: - versions_part = line.split("Available versions:")[1].strip() - if versions_part: - versions = [v.strip() for v in versions_part.split(',')] - return versions[0] if versions else None - return None - except (subprocess.TimeoutExpired, subprocess.SubprocessError): - return None + except (subprocess.TimeoutExpired, subprocess.SubprocessError): + if attempt < retries - 1: + time.sleep(2 * (attempt + 1)) + return None -def get_pypi_package_info(package: str) -> Optional[Dict]: - """Get package information from PyPI API.""" - try: - response = requests.get(f"https://pypi.org/pypi/{package}/json", timeout=30) - response.raise_for_status() - return response.json() - except requests.RequestException: - return None +def get_pypi_package_info(package: str, retries: int = 3) -> Optional[Dict]: + """Get package information from PyPI API, retrying on transient failures.""" + for attempt in range(retries): + try: + response = requests.get(f"https://pypi.org/pypi/{package}/json", timeout=30) + response.raise_for_status() + return response.json() + except requests.RequestException: + if attempt < retries - 1: + time.sleep(2 * (attempt + 1)) + return None def get_pypi_latest_version(package_info: Dict) -> str: @@ -160,8 +170,42 @@ def extract_pr_url(stdout: str) -> Optional[str]: return None +def find_open_pr_for_branch(branch: str, retries: int = 3) -> Optional[str]: + """Return the URL of an open PR with the given head branch, if any. Retries on transient failures.""" + for attempt in range(retries): + try: + result = subprocess.run([ + "gh", "pr", "list", + "--repo", REPO, + "--head", branch, + "--state", "open", + "--json", "url", + "--jq", ".[0].url", + ], capture_output=True, text=True, timeout=30) + + if result.returncode != 0: + if attempt < retries - 1: + time.sleep(2 * (attempt + 1)) + continue + return None + + return result.stdout.strip() or None + + except (subprocess.TimeoutExpired, subprocess.SubprocessError): + if attempt < retries - 1: + time.sleep(2 * (attempt + 1)) + return None + + def create_deprecation_pr(package: str, reason: str) -> Optional[str]: """Create a pull request to deprecate a package.""" + branch = f"github-actions/deprecate-{package}" + + existing_pr = find_open_pr_for_branch(branch) + if existing_pr: + print(f" [=] PR already open for {package}: {existing_pr}") + return existing_pr + try: git_run("fetch", "origin") git_run("switch", "main") @@ -185,8 +229,6 @@ def create_deprecation_pr(package: str, reason: str) -> Optional[str]: else: print(f" [!] No upstream issue found for {package}") - branch = f"github-actions/deprecate-{package}" - configure_git_identity() git_run("switch", "-c", branch) @@ -239,6 +281,8 @@ def create_deprecation_pr(package: str, reason: str) -> Optional[str]: except subprocess.CalledProcessError as e: print(f" [X] Error creating PR for {package}: {e.stderr or e}") + print(f" [?] Could not confirm whether a PR already exists for {package} " + "— please check open PRs manually") return None except Exception as e: print(f" [X] Unexpected error creating PR for {package}: {e}") @@ -361,6 +405,12 @@ def create_upgrade_pr(package: str, package_info: Dict, new_versions: List[str]) return None latest_version = new_versions[-1] + branch = f"github-actions/upgrade-{package}-{latest_version}" + + existing_pr = find_open_pr_for_branch(branch) + if existing_pr: + print(f" [=] PR already open for {package}: {existing_pr}") + return existing_pr try: configure_git_identity() @@ -368,7 +418,6 @@ def create_upgrade_pr(package: str, package_info: Dict, new_versions: List[str]) git_run("fetch", "origin") git_run("switch", "main") - branch = f"github-actions/upgrade-{package}-{latest_version}" git_run("switch", "-c", branch) pypi_package_url = get_pypi_package_url(package_info) @@ -468,6 +517,8 @@ def create_upgrade_pr(package: str, package_info: Dict, new_versions: List[str]) except subprocess.CalledProcessError as e: print(f" [X] Error creating upgrade PR for {package}: {e.stderr or e}") + print(f" [?] Could not confirm whether a PR already exists for {package} " + "— please check open PRs manually") return None except Exception as e: print(f" [X] Unexpected error creating upgrade PR for {package}: {e}") @@ -651,9 +702,11 @@ def main(): if r["status"] in ("need_upgrade", "can_deprecate") and r.get("pr_url") is None ] if pr_failures: - print(f"\n[X] PR creation failed for {len(pr_failures)} package(s): " + print(f"\n[?] Could not create (or confirm an existing) PR for " + f"{len(pr_failures)} package(s): " + ", ".join(r["package"] for r in pr_failures)) - sys.exit(1) + print(" Please review open PRs to check whether one already exists " + "for these packages.") if __name__ == "__main__": From 79252ec2967bc7a3554ec66ed2dc0331b2c9154d Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Wed, 19 Aug 2026 11:24:43 -0400 Subject: [PATCH 2/2] workflows: nightly: run on PRs when either workflow or check_versions.py is modified Run the nightly workflow on PRs when appropriate, but do a dry-run (i.e. don't actually try creating PRs) in this case so we don't open things during testing. AI-Generated: Uses Claude Sonnet 5 Signed-off-by: Trevor Gamblin --- .github/workflows/nightly.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8a6139d1..ea906979 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,6 +7,10 @@ on: schedule: - cron: '0 2 * * *' workflow_dispatch: + pull_request: + paths: + - '.github/workflows/nightly.yml' + - 'ci_scripts/check_versions.py' permissions: contents: write @@ -35,7 +39,7 @@ jobs: - name: Check versions and open PRs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: python3 ci_scripts/check_versions.py --summary --create-prs + run: python3 ci_scripts/check_versions.py --summary${{ github.event_name != 'pull_request' && ' --create-prs' || '' }} check_deprecated_packages: runs-on: ubuntu-latest