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. https://github.com/pypa/cibuildwheel/actions/runs/9956369360/job/27506182952?pr=1538 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:
@@ -1710,6 +1710,14 @@ mod tests {
|
||||
PythonRequest::parse("pp"),
|
||||
PythonRequest::Implementation(ImplementationName::PyPy)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("graalpy"),
|
||||
PythonRequest::Implementation(ImplementationName::GraalPy)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("gp"),
|
||||
PythonRequest::Implementation(ImplementationName::GraalPy)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("cp"),
|
||||
PythonRequest::Implementation(ImplementationName::CPython)
|
||||
@@ -1728,6 +1736,20 @@ mod tests {
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("graalpy3.10"),
|
||||
PythonRequest::ImplementationVersion(
|
||||
ImplementationName::GraalPy,
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("gp310"),
|
||||
PythonRequest::ImplementationVersion(
|
||||
ImplementationName::GraalPy,
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("cp38"),
|
||||
PythonRequest::ImplementationVersion(
|
||||
@@ -1749,6 +1771,20 @@ mod tests {
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("graalpy@3.10"),
|
||||
PythonRequest::ImplementationVersion(
|
||||
ImplementationName::GraalPy,
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("graalpy310"),
|
||||
PythonRequest::ImplementationVersion(
|
||||
ImplementationName::GraalPy,
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
assert_eq!(
|
||||
@@ -1819,6 +1855,18 @@ mod tests {
|
||||
.to_canonical_string(),
|
||||
"pypy@3.10"
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::Implementation(ImplementationName::GraalPy).to_canonical_string(),
|
||||
"graalpy"
|
||||
);
|
||||
assert_eq!(
|
||||
PythonRequest::ImplementationVersion(
|
||||
ImplementationName::GraalPy,
|
||||
VersionRequest::from_str("3.10").unwrap()
|
||||
)
|
||||
.to_canonical_string(),
|
||||
"graalpy@3.10"
|
||||
);
|
||||
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,6 +15,7 @@ pub enum ImplementationName {
|
||||
#[default]
|
||||
CPython,
|
||||
PyPy,
|
||||
GraalPy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
|
||||
@@ -25,13 +26,14 @@ pub enum LenientImplementationName {
|
||||
|
||||
impl ImplementationName {
|
||||
pub(crate) fn possible_names() -> impl Iterator<Item = &'static str> {
|
||||
["cpython", "pypy", "cp", "pp"].into_iter()
|
||||
["cpython", "pypy", "graalpy", "cp", "pp", "gp"].into_iter()
|
||||
}
|
||||
|
||||
pub fn pretty(self) -> &'static str {
|
||||
match self {
|
||||
Self::CPython => "CPython",
|
||||
Self::PyPy => "PyPy",
|
||||
Self::GraalPy => "GraalPy",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +52,7 @@ impl From<&ImplementationName> for &'static str {
|
||||
match v {
|
||||
ImplementationName::CPython => "cpython",
|
||||
ImplementationName::PyPy => "pypy",
|
||||
ImplementationName::GraalPy => "graalpy",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +76,7 @@ impl FromStr for ImplementationName {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"cpython" | "cp" => Ok(Self::CPython),
|
||||
"pypy" | "pp" => Ok(Self::PyPy),
|
||||
"graalpy" | "gp" => Ok(Self::GraalPy),
|
||||
_ => Err(Error::UnknownImplementation(s.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user