@@ -148,10 +148,10 @@ index 6617644..ad52082 100644
148148 return self._ireq
149149diff --git a/pip/_internal/utils/graalpy.py b/pip/_internal/utils/graalpy.py
150150new file mode 100644
151- index 0000000..9e79fb0
151+ index 0000000..cdc4852
152152--- /dev/null
153153+++ b/pip/_internal/utils/graalpy.py
154- @@ -0,0 +1,334 @@
154+ @@ -0,0 +1,354 @@
155155+ import abc
156156+ import logging
157157+ import os
@@ -179,6 +179,7 @@ index 0000000..9e79fb0
179179+ DEFAULT_PATCHES_URL = f'https://raw.githubusercontent.com/oracle/graalpython/refs/heads/github/patches/{VERSION_PARAMETER}/graalpython/lib-graalpython/patches/'
180180+
181181+ PATCHES_URL = os.environ.get('PIP_GRAALPY_PATCHES_URL', DEFAULT_PATCHES_URL)
182+ + DISABLED_PATCHES_URL = 'disabled'
182183+ DISABLE_PATCHING = os.environ.get('PIP_GRAALPY_DISABLE_PATCHING', '').lower() in ('true', '1')
183184+ DISABLE_VERSION_SELECTION = os.environ.get('PIP_GRAALPY_DISABLE_VERSION_SELECTION', '').lower() in ('true', '1')
184185+
@@ -197,6 +198,10 @@ index 0000000..9e79fb0
197198+ pass
198199+
199200+
201+ + class RepositoryNotFound(RepositoryException):
202+ + pass
203+ +
204+ +
200205+ class AbstractPatchRepository(metaclass=abc.ABCMeta):
201206+ def __init__(self, metadata: dict):
202207+ self._repository = metadata
@@ -262,13 +267,14 @@ index 0000000..9e79fb0
262267+ def __init__(self, patches_path: Path, repository_data: dict):
263268+ super().__init__(repository_data)
264269+ self.patches_path = patches_path
265- + logger.debug("Loaded GraalPy patch repository from %s", patches_path)
266270+
267271+ @classmethod
268272+ def from_path(cls, patches_path: Path):
269273+ try:
270274+ with open(patches_path / METADATA_FILENAME) as f:
271275+ metadata_content = f.read()
276+ + except FileNotFoundError:
277+ + raise RepositoryNotFound(f"'{METADATA_FILENAME}' does not exist")
272278+ except OSError as e:
273279+ raise RepositoryException(f"'{METADATA_FILENAME}' cannot be read: {e}")
274280+ return cls(patches_path, cls.metadata_from_string(metadata_content))
@@ -282,7 +288,6 @@ index 0000000..9e79fb0
282288+ def __init__(self, patches_url: str, repository_data: dict):
283289+ super().__init__(repository_data)
284290+ self.patches_url = patches_url
285- + logger.debug("Loaded GraalPy patch repository from %s", patches_url)
286291+
287292+ @staticmethod
288293+ def get_session():
@@ -294,10 +299,14 @@ index 0000000..9e79fb0
294299+ try:
295300+ url = url_for_file(patches_url, METADATA_FILENAME)
296301+ response = cls.get_session().get(url)
302+ + if response.status_code == 404:
303+ + raise RepositoryNotFound(f"'{METADATA_FILENAME}' not found")
297304+ response.raise_for_status()
298- + metadata_content = response.content.decode('utf-8')
305+ + if not response.encoding:
306+ + response.encoding = 'utf-8'
307+ + metadata_content = response.text
299308+ except Exception as e:
300- + raise RepositoryException(f"'{METADATA_FILENAME} cannot be retrieved': {e}")
309+ + raise RepositoryException(f"'{METADATA_FILENAME}' cannot be retrieved': {e}")
301310+ return cls(patches_url, cls.metadata_from_string(metadata_content))
302311+
303312+ @contextmanager
@@ -310,9 +319,11 @@ index 0000000..9e79fb0
310319+ yield None
311320+ else:
312321+ with tempfile.TemporaryDirectory() as tempdir:
322+ + if not response.encoding:
323+ + response.encoding = 'utf-8'
313324+ patch_file = Path(tempdir) / patch_name
314- + with open(patch_file, 'wb ') as f:
315- + f.write(response.content )
325+ + with open(patch_file, 'w ') as f:
326+ + f.write(response.text )
316327+ yield patch_file
317328+
318329+
@@ -339,12 +350,19 @@ index 0000000..9e79fb0
339350+ else:
340351+ logger.debug("Skipping versioned GraalPy patch repository on snapshot build")
341352+ patches_url = None
342- + if patches_url:
353+ + if patches_url and patches_url != DISABLED_PATCHES_URL:
354+ + logger.info(
355+ + "Loading GraalPy post-release patch repository from %s. "
356+ + "This can be controlled with PIP_GRAALPY_PATCHES_URL environment variable. Set to '%s' to disable",
357+ + patches_url, DISABLED_PATCHES_URL)
343358+ try:
344359+ return repository_from_url_or_path(patches_url)
345360+ except RepositoryException as e:
346- + logger.warning("Failed to load GraalPy patch repository from %s: %s", patches_url, e)
347- + logger.warning("Falling back to internal GraalPy patch repository")
361+ + if patches_url == DEFAULT_PATCHES_URL and isinstance(e, RepositoryNotFound):
362+ + logger.info("No post-release patch repository published yet")
363+ + else:
364+ + logger.warning("Failed to load GraalPy patch repository: %s", e)
365+ + logger.warning("Falling back to bundled GraalPy patch repository")
348366+ try:
349367+ return LocalPatchRepository.from_path(DEFAULT_PATCHES_PATH)
350368+ except RepositoryException as e:
@@ -421,6 +439,8 @@ index 0000000..9e79fb0
421439+ "WARNING: GraalPy needs the 'patch' utility to apply compatibility patches. Please install it using your system's package manager.")
422440+ except subprocess.CalledProcessError:
423441+ logger.warning(f"Applying GraalPy patch failed for {name}. The package may still work.")
442+ + except Exception:
443+ + logger.exception(f"Failed to execute patch utility")
424444+ elif version_specs := repository.get_suggested_version_specs(name):
425445+ logger.info("We have patches to make this package work on GraalVM for some version(s).")
426446+ logger.info("If installing or running fails, consider using one of the versions that we have patches for:")
0 commit comments