From f4390ebf8e81d19d620d57a945042b37ede691ad Mon Sep 17 00:00:00 2001 From: Jean-Christophe Morin Date: Tue, 21 Nov 2023 21:17:16 -0500 Subject: [PATCH] Add support for installing local wheels Signed-off-by: Jean-Christophe Morin --- src/rez_pip/cli.py | 7 ++++++- src/rez_pip/pip.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/rez_pip/cli.py b/src/rez_pip/cli.py index e5547b1..914f519 100644 --- a/src/rez_pip/cli.py +++ b/src/rez_pip/cli.py @@ -191,9 +191,14 @@ def _run(args: argparse.Namespace, pipArgs: typing.List[str], pipWorkArea: str) _LOG.info(f"Resolved {len(packages)} dependencies for python {pythonVersion}") + localWheels = [package for package in packages if package.isLocal] + remoteWheels = [package for package in packages if not package.isLocal] + # TODO: Should we postpone downloading to the last minute if we can? _LOG.info("[bold]Downloading...") - wheels = rez_pip.download.downloadPackages(packages, wheelsDir) + wheels = rez_pip.download.downloadPackages(remoteWheels, wheelsDir) + wheels += [wheel.path for wheel in localWheels] + _LOG.info(f"[bold]Downloaded {len(wheels)} wheels") dists: typing.Dict[importlib_metadata.Distribution, bool] = {} diff --git a/src/rez_pip/pip.py b/src/rez_pip/pip.py index b02cfaf..62b9079 100644 --- a/src/rez_pip/pip.py +++ b/src/rez_pip/pip.py @@ -3,10 +3,12 @@ import json import typing import logging +import pathlib import tempfile import itertools import subprocess import dataclasses +import urllib.parse import dataclasses_json @@ -57,6 +59,36 @@ def name(self) -> str: def version(self) -> str: return self.metadata.version + def isLocal(self) -> bool: + """Returns True if the wheel is a local file""" + return urllib.parse.urlparse(self.download_info.url).scheme == 'file' + + @property + def path(self) -> str: + if not self.isLocal(): + raise RuntimeError("Cannot be path of a non local wheel") + + # Taken from https://github.com/python/cpython/pull/107640 + path = self.download_info.url[5:] + if path[:3] == '///': + # Remove empty authority + path = path[2:] + elif path[:12] == '//localhost/': + # Remove 'localhost' authority + path = path[11:] + if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'): + # Remove slash before DOS device/UNC path + path = path[1:] + if path[1:2] == '|': + # Replace bar with colon in DOS drive + path = path[:1] + ':' + path[2:] + + path = pathlib.PurePath(os.fsdecode(urllib.parse.unquote_to_bytes(path))) + if not path.is_absolute(): + raise ValueError(f"URI is not absolute: {self.download_info.url!r}") + + return os.fspath(path) + def getBundledPip() -> str: return os.path.join(os.path.dirname(rez_pip.data.__file__), "pip.pyz") @@ -95,7 +127,6 @@ def getPackages( "--ignore-installed", f"--python-version={pythonVersion}" if pythonVersion else "", "--only-binary=:all:", - "--target=/tmp/asd", "--disable-pip-version-check", "--report", # This is the "magic". Pip will generate a JSON with all the resolved URLs. tmpFile,