mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-13 12:02:17 +00:00
Add PyPy finder (#5337)
## Summary This PR adds PyPy finder and adds PyPy to uv managed Python versions. ## Test Plan ```console $ cargo run -- python install ```
This commit is contained in:
parent
96b24345eb
commit
7ddf67a72b
7 changed files with 2948 additions and 23 deletions
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
|
@ -579,10 +579,6 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "pypy3.9"
|
|
||||||
|
|
||||||
- name: "Download binary"
|
- name: "Download binary"
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
@ -591,12 +587,12 @@ jobs:
|
||||||
- name: "Prepare binary"
|
- name: "Prepare binary"
|
||||||
run: chmod +x ./uv
|
run: chmod +x ./uv
|
||||||
|
|
||||||
- name: PyPy info
|
- name: "Install PyPy"
|
||||||
run: which pypy
|
run: ./uv python install pypy3.9
|
||||||
|
|
||||||
- name: "Create a virtual environment"
|
- name: "Create a virtual environment"
|
||||||
run: |
|
run: |
|
||||||
./uv venv -p $(which pypy)
|
./uv venv -p pypy3.9 --python-preference only-managed
|
||||||
|
|
||||||
- name: "Check for executables"
|
- name: "Check for executables"
|
||||||
run: |
|
run: |
|
||||||
|
@ -645,22 +641,17 @@ jobs:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "pypy3.9"
|
|
||||||
|
|
||||||
- name: "Download binary"
|
- name: "Download binary"
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: uv-windows-${{ github.sha }}
|
name: uv-windows-${{ github.sha }}
|
||||||
|
|
||||||
- name: PyPy info
|
- name: "Install PyPy"
|
||||||
run: Get-Command pypy
|
run: .\uv.exe python install pypy3.9
|
||||||
|
|
||||||
- name: "Create a virtual environment"
|
- name: "Create a virtual environment"
|
||||||
run: |
|
run: |
|
||||||
$pypy = (Get-Command pypy).source
|
.\uv.exe venv -p pypy3.9 --python-preference only-managed
|
||||||
.\uv.exe venv -p $pypy
|
|
||||||
|
|
||||||
- name: "Check for executables"
|
- name: "Check for executables"
|
||||||
shell: python
|
shell: python
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -322,6 +322,96 @@ class CPythonFinder(Finder):
|
||||||
return pref
|
return pref
|
||||||
|
|
||||||
|
|
||||||
|
class PyPyFinder(Finder):
|
||||||
|
implementation = ImplementationName.PYPY
|
||||||
|
|
||||||
|
RELEASE_URL = "https://raw.githubusercontent.com/pypy/pypy/main/pypy/tool/release/versions.json"
|
||||||
|
CHECKSUM_URL = (
|
||||||
|
"https://raw.githubusercontent.com/pypy/pypy.org/main/pages/checksums.rst"
|
||||||
|
)
|
||||||
|
|
||||||
|
_checksum_re = re.compile(
|
||||||
|
r"^\s*(?P<checksum>\w{64})\s+(?P<filename>pypy.+)$", re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
ARCH_MAPPING = {
|
||||||
|
"x64": "x86_64",
|
||||||
|
"x86": "i686",
|
||||||
|
"i686": "i686",
|
||||||
|
"aarch64": "aarch64",
|
||||||
|
"arm64": "aarch64",
|
||||||
|
"s390x": "s390x",
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORM_MAPPING = {
|
||||||
|
"win32": "windows",
|
||||||
|
"win64": "windows",
|
||||||
|
"linux": "linux",
|
||||||
|
"darwin": "darwin",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, client: httpx.AsyncClient):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
async def find(self) -> list[PythonDownload]:
|
||||||
|
downloads = await self._fetch_downloads()
|
||||||
|
await self._fetch_checksums(downloads)
|
||||||
|
return downloads
|
||||||
|
|
||||||
|
async def _fetch_downloads(self) -> list[PythonDownload]:
|
||||||
|
resp = await self.client.get(self.RELEASE_URL)
|
||||||
|
resp.raise_for_status()
|
||||||
|
versions = resp.json()
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for version in versions:
|
||||||
|
if not version["stable"]:
|
||||||
|
continue
|
||||||
|
python_version = Version.from_str(version["python_version"])
|
||||||
|
if python_version < (3, 7, 0):
|
||||||
|
continue
|
||||||
|
for file in version["files"]:
|
||||||
|
arch = self._normalize_arch(file["arch"])
|
||||||
|
platform = self._normalize_os(file["platform"])
|
||||||
|
libc = "gnu" if platform == "linux" else "none"
|
||||||
|
download = PythonDownload(
|
||||||
|
version=python_version,
|
||||||
|
triple=PlatformTriple(
|
||||||
|
arch=arch,
|
||||||
|
platform=platform,
|
||||||
|
libc=libc,
|
||||||
|
),
|
||||||
|
flavor="",
|
||||||
|
implementation=self.implementation,
|
||||||
|
filename=file["filename"],
|
||||||
|
url=file["download_url"],
|
||||||
|
)
|
||||||
|
# Only keep the latest pypy version of each arch/platform
|
||||||
|
if (python_version, arch, platform) not in results:
|
||||||
|
results[(python_version, arch, platform)] = download
|
||||||
|
|
||||||
|
return list(results.values())
|
||||||
|
|
||||||
|
def _normalize_arch(self, arch: str) -> str:
|
||||||
|
return self.ARCH_MAPPING.get(arch, arch)
|
||||||
|
|
||||||
|
def _normalize_os(self, os: str) -> str:
|
||||||
|
return self.PLATFORM_MAPPING.get(os, os)
|
||||||
|
|
||||||
|
async def _fetch_checksums(self, downloads: list[PythonDownload]) -> None:
|
||||||
|
logging.info("Fetching PyPy checksums")
|
||||||
|
resp = await self.client.get(self.CHECKSUM_URL)
|
||||||
|
resp.raise_for_status()
|
||||||
|
text = resp.text
|
||||||
|
|
||||||
|
checksums = {}
|
||||||
|
for match in self._checksum_re.finditer(text):
|
||||||
|
checksums[match.group("filename")] = match.group("checksum")
|
||||||
|
|
||||||
|
for download in downloads:
|
||||||
|
download.sha256 = checksums.get(download.filename)
|
||||||
|
|
||||||
|
|
||||||
def render(downloads: list[PythonDownload]) -> None:
|
def render(downloads: list[PythonDownload]) -> None:
|
||||||
"""Render `download-metadata.json`."""
|
"""Render `download-metadata.json`."""
|
||||||
|
|
||||||
|
@ -371,9 +461,9 @@ async def find() -> None:
|
||||||
headers["Authorization"] = "Bearer " + token
|
headers["Authorization"] = "Bearer " + token
|
||||||
client = httpx.AsyncClient(follow_redirects=True, headers=headers, timeout=15)
|
client = httpx.AsyncClient(follow_redirects=True, headers=headers, timeout=15)
|
||||||
|
|
||||||
# TODO: Add PyPyFinder
|
|
||||||
finders = [
|
finders = [
|
||||||
CPythonFinder(client),
|
CPythonFinder(client),
|
||||||
|
PyPyFinder(client),
|
||||||
]
|
]
|
||||||
downloads = []
|
downloads = []
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -314,15 +314,23 @@ impl ManagedPythonInstallation {
|
||||||
/// standard `EXTERNALLY-MANAGED` file.
|
/// standard `EXTERNALLY-MANAGED` file.
|
||||||
pub fn ensure_externally_managed(&self) -> Result<(), Error> {
|
pub fn ensure_externally_managed(&self) -> Result<(), Error> {
|
||||||
// Construct the path to the `stdlib` directory.
|
// Construct the path to the `stdlib` directory.
|
||||||
let stdlib = if cfg!(windows) {
|
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
|
||||||
self.python_dir().join("Lib")
|
self.python_dir().join("Lib")
|
||||||
} else {
|
} else {
|
||||||
self.python_dir()
|
let python = if matches!(
|
||||||
.join("lib")
|
self.key.implementation,
|
||||||
.join(format!("python{}", self.key.version().python_version()))
|
LenientImplementationName::Known(ImplementationName::PyPy)
|
||||||
|
) {
|
||||||
|
format!("pypy{}", self.key.version().python_version())
|
||||||
|
} else {
|
||||||
|
format!("python{}", self.key.version().python_version())
|
||||||
|
};
|
||||||
|
self.python_dir().join("lib").join(python)
|
||||||
};
|
};
|
||||||
|
|
||||||
let file = stdlib.join("EXTERNALLY-MANAGED");
|
let file = stdlib.join("EXTERNALLY-MANAGED");
|
||||||
fs_err::write(file, EXTERNALLY_MANAGED)?;
|
fs_err::write(file, EXTERNALLY_MANAGED)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ def prepare_name(name: str) -> str:
|
||||||
match name:
|
match name:
|
||||||
case "cpython":
|
case "cpython":
|
||||||
return "CPython"
|
return "CPython"
|
||||||
|
case "pypy":
|
||||||
|
return "PyPy"
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Unknown implementation name: {name}")
|
raise ValueError(f"Unknown implementation name: {name}")
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,12 @@ Many Python version request formats are supported:
|
||||||
- `<implementation><version-specifier>` e.g. `cpython>=3.12,<3.13`
|
- `<implementation><version-specifier>` e.g. `cpython>=3.12,<3.13`
|
||||||
- `<implementation>-<version>-<os>-<arch>-<libc>` e.g. `cpython-3.12.3-macos-aarch64-none`
|
- `<implementation>-<version>-<os>-<arch>-<libc>` e.g. `cpython-3.12.3-macos-aarch64-none`
|
||||||
|
|
||||||
At this time, only CPython downloads are supported. However, PyPy support is planned.
|
|
||||||
|
|
||||||
## Installing a Python version
|
## Installing a Python version
|
||||||
|
|
||||||
Sometimes it is preferable to install the Python versions before they are needed.
|
Sometimes it is preferable to install the Python versions before they are needed.
|
||||||
|
|
||||||
|
uv bundles a list of downloadable CPython and PyPy distributions for macOS, Linux, and Windows.
|
||||||
|
|
||||||
To install a Python version at a specific version:
|
To install a Python version at a specific version:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -122,8 +122,10 @@ If a specific Python version is requested, e.g. `--python 3.7`, additional execu
|
||||||
|
|
||||||
## Python distributions
|
## Python distributions
|
||||||
|
|
||||||
Python does not publish official distributable binaries, uv uses third-party standalone distributions from the [`python-build-standalone`](https://github.com/indygreg/python-build-standalone) project. The project is partially maintained by the uv maintainers and is used by many other Python projects.
|
Python does not publish official distributable CPython binaries, uv uses third-party standalone distributions from the [`python-build-standalone`](https://github.com/indygreg/python-build-standalone) project. The project is partially maintained by the uv maintainers and is used by many other Python projects.
|
||||||
|
|
||||||
The Python distributions are self-contained and highly-portable. Additionally, these distributions have various build-time optimizations enabled to ensure they are performant.
|
The Python distributions are self-contained and highly-portable. Additionally, these distributions have various build-time optimizations enabled to ensure they are performant.
|
||||||
|
|
||||||
These distributions have some behavior quirks, generally as a consequence of portability. See the [`python-build-standalone` quirks](https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html) documentation for details.
|
These distributions have some behavior quirks, generally as a consequence of portability. See the [`python-build-standalone` quirks](https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html) documentation for details.
|
||||||
|
|
||||||
|
PyPy distributions are provided by the PyPy project.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue