diff --git a/crates/gourgeist/src/bare.rs b/crates/gourgeist/src/bare.rs index 0ebaa5852..fac848f53 100644 --- a/crates/gourgeist/src/bare.rs +++ b/crates/gourgeist/src/bare.rs @@ -191,7 +191,8 @@ pub fn create_bare_venv( .replace( "{{ RELATIVE_SITE_PACKAGES }}", &format!( - "../lib/python{}.{}/site-packages", + "../lib/{}{}.{}/site-packages", + interpreter.site_packages_python(), interpreter.python_major(), interpreter.python_minor(), ), @@ -278,7 +279,8 @@ pub fn create_bare_venv( location .join("lib") .join(format!( - "python{}.{}", + "{}{}.{}", + interpreter.site_packages_python(), interpreter.python_major(), interpreter.python_minor(), )) diff --git a/crates/uv-interpreter/src/interpreter.rs b/crates/uv-interpreter/src/interpreter.rs index 3f0d16ad6..f9ebce00c 100644 --- a/crates/uv-interpreter/src/interpreter.rs +++ b/crates/uv-interpreter/src/interpreter.rs @@ -93,9 +93,21 @@ impl Interpreter { // structure, which allows us to avoid querying the interpreter for the `sysconfig` // paths. sysconfig_paths: SysconfigPaths { - purelib: layout.site_packages(&venv_root, self.python_tuple()), - platlib: layout.site_packages(&venv_root, self.python_tuple()), - platstdlib: layout.platstdlib(&venv_root, self.python_tuple()), + purelib: layout.site_packages( + &venv_root, + self.site_packages_python(), + self.python_tuple(), + ), + platlib: layout.site_packages( + &venv_root, + self.site_packages_python(), + self.python_tuple(), + ), + platstdlib: layout.platstdlib( + &venv_root, + self.site_packages_python(), + self.python_tuple(), + ), scripts: layout.scripts(&venv_root), data: layout.data(&venv_root), ..self.sysconfig_paths @@ -399,6 +411,33 @@ impl Interpreter { &self.sysconfig_paths.stdlib } + /// Return the name of the Python directory used to build the path to the + /// `site-packages` directory. + /// + /// If one could not be determined, then `python` is returned. + pub fn site_packages_python(&self) -> &str { + if self.implementation_name() == "pypy" { + "pypy" + } else { + "python" + } + + // This implementation sniffs out what the directory name + // might be from sysconfig, but at the time of writing, this + // seems a little more risky than the simpler but less robust + // implementation above. + /* + const FALLBACK: &str = "python"; + let Some(base) = self.include().file_name().and_then(|name| name.to_str()) else { + return FALLBACK; + }; + base.char_indices() + .take_while(|(_, ch)| ch.is_ascii_alphabetic()) + .last() + .map_or(FALLBACK, |(end, ch)| &base[..end + ch.len_utf8()]) + */ + } + /// Return the [`Layout`] environment used to install wheels into this interpreter. pub fn layout(&self) -> Layout { Layout { @@ -412,7 +451,8 @@ impl Interpreter { // If the interpreter is a venv, then the `include` directory has a different structure. // See: https://github.com/pypa/pip/blob/0ad4c94be74cc24874c6feb5bb3c2152c398a18e/src/pip/_internal/locations/_sysconfig.py#L172 self.prefix.join("include").join("site").join(format!( - "python{}.{}", + "{}{}.{}", + self.site_packages_python(), self.python_major(), self.python_minor() )) diff --git a/crates/uv-interpreter/src/virtualenv_layout.rs b/crates/uv-interpreter/src/virtualenv_layout.rs index 81673657b..a05535de7 100644 --- a/crates/uv-interpreter/src/virtualenv_layout.rs +++ b/crates/uv-interpreter/src/virtualenv_layout.rs @@ -41,13 +41,18 @@ impl<'a> VirtualenvLayout<'a> { } /// Returns the path to the `site-packages` directory inside a virtual environment. - pub(crate) fn site_packages(&self, venv_root: impl AsRef, version: (u8, u8)) -> PathBuf { + pub(crate) fn site_packages( + &self, + venv_root: impl AsRef, + site_packages_python: &str, + version: (u8, u8), + ) -> PathBuf { let venv = venv_root.as_ref(); if matches!(self.0.os(), Os::Windows) { venv.join("Lib").join("site-packages") } else { venv.join("lib") - .join(format!("python{}.{}", version.0, version.1)) + .join(format!("{site_packages_python}{}.{}", version.0, version.1)) .join("site-packages") } } @@ -60,13 +65,18 @@ impl<'a> VirtualenvLayout<'a> { /// Returns the path to the `platstdlib` directory inside a virtual environment. #[allow(clippy::unused_self)] - pub(crate) fn platstdlib(&self, venv_root: impl AsRef, version: (u8, u8)) -> PathBuf { + pub(crate) fn platstdlib( + &self, + venv_root: impl AsRef, + site_packages_python: &str, + version: (u8, u8), + ) -> PathBuf { let venv = venv_root.as_ref(); if matches!(self.0.os(), Os::Windows) { venv.join("Lib") } else { venv.join("lib") - .join(format!("python{}.{}", version.0, version.1)) + .join(format!("{site_packages_python}{}.{}", version.0, version.1)) .join("site-packages") } }