From 7bb3ef8fc2246681de79513d62f179e3d36eba23 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 16 Jul 2024 14:38:08 +0200 Subject: [PATCH] Add GraalPy support --- crates/platform-tags/src/tags.rs | 14 +++ crates/uv-python/src/discovery.rs | 48 ++++++++ crates/uv-python/src/implementation.rs | 6 +- crates/uv-python/src/lib.rs | 156 +++++++++++++++++++++++++ crates/uv-virtualenv/src/bare.rs | 4 + 5 files changed, 227 insertions(+), 1 deletion(-) diff --git a/crates/platform-tags/src/tags.rs b/crates/platform-tags/src/tags.rs index 09ef84e181d45..b65f610be6686 100644 --- a/crates/platform-tags/src/tags.rs +++ b/crates/platform-tags/src/tags.rs @@ -298,6 +298,7 @@ impl std::fmt::Display for Tags { enum Implementation { CPython { gil_disabled: bool }, PyPy, + GraalPy, Pyston, } @@ -310,6 +311,8 @@ impl Implementation { Self::CPython { .. } => format!("cp{}{}", python_version.0, python_version.1), // Ex) `pp39` Self::PyPy => format!("pp{}{}", python_version.0, python_version.1), + // Ex) `graalpy310` + Self::GraalPy => format!("graalpy{}{}", python_version.0, python_version.1), // Ex) `pt38`` Self::Pyston => format!("pt{}{}", python_version.0, python_version.1), } @@ -342,6 +345,16 @@ impl Implementation { implementation_version.0, implementation_version.1 ), + // Ex) `graalpy310_graalpy240_310_native + Self::GraalPy => format!( + "graalpy{}{}_graalpy{}{}_{}{}_native", + python_version.0, + python_version.1, + implementation_version.0, + implementation_version.1, + python_version.0, + python_version.1 + ), // Ex) `pyston38-pyston_23` Self::Pyston => format!( "pyston{}{}-pyston_{}{}", @@ -361,6 +374,7 @@ impl Implementation { // Known and supported implementations. "cpython" => Ok(Self::CPython { gil_disabled }), "pypy" => Ok(Self::PyPy), + "graalpy" => Ok(Self::GraalPy), "pyston" => Ok(Self::Pyston), // Known but unsupported implementations. "python" => Err(TagsError::UnsupportedImplementation(name.to_string())), diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 60b76666b0104..a85ef0ac76acb 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -1640,6 +1640,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) @@ -1658,6 +1666,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( @@ -1679,6 +1701,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!( @@ -1749,6 +1785,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!( diff --git a/crates/uv-python/src/implementation.rs b/crates/uv-python/src/implementation.rs index 869ddbc6a18c6..3ce36fd15a764 100644 --- a/crates/uv-python/src/implementation.rs +++ b/crates/uv-python/src/implementation.rs @@ -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 { - ["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())), } } diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 025f87d03b035..f75a66b32ad71 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -1869,4 +1869,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(()) + } } diff --git a/crates/uv-virtualenv/src/bare.rs b/crates/uv-virtualenv/src/bare.rs index 34827807191fa..679b320c4833e 100644 --- a/crates/uv-virtualenv/src/bare.rs +++ b/crates/uv-virtualenv/src/bare.rs @@ -178,6 +178,10 @@ pub fn create_bare_venv( )?; uv_fs::replace_symlink("python", scripts.join("pypy"))?; } + + if interpreter.markers().implementation_name() == "graalpy" { + uv_fs::replace_symlink("python", scripts.join("graalpy"))?; + } } // No symlinking on Windows, at least not on a regular non-dev non-admin Windows install.