Add GraalPy support (#5141)

<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Currently, `uv` refuses to install anything on GraalPy. This is
currently blocking GraalPy testing with cibuildwheel, since manylinux
includes both `uv` and `graalpy` (but doesn't test with `uv`), whereas
cibuildwheel defaults to `uv`. See e.g.
2750618295
where it gives
```
      + python -m build /project/sample_proj --wheel --outdir=/tmp/cibuildwheel/built_wheel --installer=uv
  * Creating isolated environment: venv+uv...
  * Using external uv from /usr/local/bin/uv
  * Installing packages in isolated environment:
    - setuptools >= 40.8.0
  > /usr/local/bin/uv pip install "setuptools >= 40.8.0"
  < error: Unknown implementation: `graalpy`
```

## Test Plan

I simply based the GraalPy support on PyPy and added some small tests.
I'm open to discussing how to test this. GraalPy is available for
manylinux images and with setup-python, so we should be able to add
tests against it to the CI. I locally confirmed by installing `uv` into
a GraalPy venv and then trying things like `uv pip install Pillow` and
testing those extensions.
This commit is contained in:
Tim Felgentreff 2024-07-19 02:28:28 +02:00 committed by GitHub
parent 54bca18184
commit 24a0268675
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 397 additions and 8 deletions

View file

@ -1896,4 +1896,160 @@ mod tests {
Ok(())
}
#[test]
fn find_python_graalpy() -> Result<()> {
let mut context = TestContext::new()?;
context.add_python_interpreters(&[(
true,
ImplementationName::GraalPy,
"graalpy",
"3.10.0",
)])?;
let result = context.run(|| {
find_python_installation(
&PythonRequest::Any,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})?;
assert!(
matches!(result, Err(PythonNotFound { .. })),
"We should not the graalpy interpreter if not named `python` or requested; got {result:?}"
);
// But we should find it
context.reset_search_path();
context.add_python_interpreters(&[(
true,
ImplementationName::GraalPy,
"python",
"3.10.1",
)])?;
let python = context.run(|| {
find_python_installation(
&PythonRequest::Any,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.1",
"We should find the graalpy interpreter if it's the only one"
);
let python = context.run(|| {
find_python_installation(
&PythonRequest::parse("graalpy"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.1",
"We should find the graalpy interpreter if it's requested"
);
Ok(())
}
#[test]
fn find_python_graalpy_request_ignores_cpython() -> Result<()> {
let mut context = TestContext::new()?;
context.add_python_interpreters(&[
(true, ImplementationName::CPython, "python", "3.10.0"),
(true, ImplementationName::GraalPy, "graalpy", "3.10.1"),
])?;
let python = context.run(|| {
find_python_installation(
&PythonRequest::parse("graalpy"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.1",
"We should skip the CPython interpreter"
);
let python = context.run(|| {
find_python_installation(
&PythonRequest::Any,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.0",
"We should take the first interpreter without a specific request"
);
Ok(())
}
#[test]
fn find_python_graalpy_prefers_executable_with_implementation_name() -> Result<()> {
let mut context = TestContext::new()?;
// We should prefer `graalpy` executables over `python` executables in the same directory
// even if they are both graalpy
TestContext::create_mock_interpreter(
&context.tempdir.join("python"),
&PythonVersion::from_str("3.10.0").unwrap(),
ImplementationName::GraalPy,
true,
)?;
TestContext::create_mock_interpreter(
&context.tempdir.join("graalpy"),
&PythonVersion::from_str("3.10.1").unwrap(),
ImplementationName::GraalPy,
true,
)?;
context.add_to_search_path(context.tempdir.to_path_buf());
let python = context.run(|| {
find_python_installation(
&PythonRequest::parse("graalpy@3.10"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.1",
);
// But `python` executables earlier in the search path will take precedence
context.reset_search_path();
context.add_python_interpreters(&[
(true, ImplementationName::GraalPy, "python", "3.10.2"),
(true, ImplementationName::GraalPy, "graalpy", "3.10.3"),
])?;
let python = context.run(|| {
find_python_installation(
&PythonRequest::parse("graalpy@3.10"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.2",
);
Ok(())
}
}