Search in both purelib and platlib for site-packages population (#2537)

## Summary

In reality, there's no such thing as the `site-packages` directory for a
given virtualenv. Rather, Python defines both `purelib` and `platlib`,
where the former is for pure-Python packages and the latter is for
packages that contain native code. These are almost always set to the
same thing... but they don't _have_ to be, and in fact of Fedora they
are not.

This PR changes the `site_packages` method to return an iterator of
directories.

Closes https://github.com/astral-sh/uv/issues/2527.
This commit is contained in:
Charlie Marsh 2024-03-18 20:06:16 -07:00 committed by GitHub
parent 142b2de191
commit ba14f69676
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 67 additions and 51 deletions

View file

@ -42,47 +42,51 @@ impl<'a> SitePackages<'a> {
let mut by_name = FxHashMap::default();
let mut by_url = FxHashMap::default();
// Read the site-packages directory.
let site_packages = match fs::read_dir(venv.site_packages()) {
Ok(site_packages) => site_packages,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(Self {
venv,
distributions,
by_name,
by_url,
});
}
Err(err) => return Err(err).context("Failed to read site-packages directory"),
};
// Index all installed packages by name.
for entry in site_packages {
let entry = entry?;
if entry.file_type()?.is_dir() {
let path = entry.path();
let Some(dist_info) = InstalledDist::try_from_path(&path)
.with_context(|| format!("Failed to read metadata: from {}", path.display()))?
else {
continue;
};
let idx = distributions.len();
// Index the distribution by name.
by_name
.entry(dist_info.name().clone())
.or_insert_with(Vec::new)
.push(idx);
// Index the distribution by URL.
if let Some(url) = dist_info.as_editable() {
by_url.entry(url.clone()).or_insert_with(Vec::new).push(idx);
for site_packages in venv.site_packages() {
// Read the site-packages directory.
let site_packages = match fs::read_dir(site_packages) {
Ok(site_packages) => site_packages,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(Self {
venv,
distributions,
by_name,
by_url,
});
}
Err(err) => return Err(err).context("Failed to read site-packages directory"),
};
// Add the distribution to the database.
distributions.push(Some(dist_info));
// Index all installed packages by name.
for entry in site_packages {
let entry = entry?;
if entry.file_type()?.is_dir() {
let path = entry.path();
let Some(dist_info) =
InstalledDist::try_from_path(&path).with_context(|| {
format!("Failed to read metadata: from {}", path.display())
})?
else {
continue;
};
let idx = distributions.len();
// Index the distribution by name.
by_name
.entry(dist_info.name().clone())
.or_insert_with(Vec::new)
.push(idx);
// Index the distribution by URL.
if let Some(url) = dist_info.as_editable() {
by_url.entry(url.clone()).or_insert_with(Vec::new).push(idx);
}
// Add the distribution to the database.
distributions.push(Some(dist_info));
}
}
}

View file

@ -88,9 +88,18 @@ impl PythonEnvironment {
self.interpreter.sys_executable()
}
/// Returns the path to the `site-packages` directory inside a virtual environment.
pub fn site_packages(&self) -> &Path {
self.interpreter.purelib()
/// Returns an iterator over the `site-packages` directories inside a virtual environment.
///
/// In most cases, `purelib` and `platlib` will be the same, and so the iterator will contain
/// a single element; however, in some distributions, they may be different.
pub fn site_packages(&self) -> impl Iterator<Item = &Path> {
std::iter::once(self.interpreter.purelib()).chain(
if self.interpreter.purelib() == self.interpreter.platlib() {
None
} else {
Some(self.interpreter.platlib())
},
)
}
/// Returns the path to the `bin` directory inside a virtual environment.

View file

@ -129,14 +129,17 @@ pub(super) async fn compile_bytecode(
printer: Printer,
) -> anyhow::Result<()> {
let start = std::time::Instant::now();
let files = compile_tree(venv.site_packages(), venv.python_executable(), cache.root())
.await
.with_context(|| {
format!(
"Failed to bytecode compile {}",
venv.site_packages().simplified_display()
)
})?;
let mut files = 0;
for site_packages in venv.site_packages() {
files += compile_tree(site_packages, venv.python_executable(), cache.root())
.await
.with_context(|| {
format!(
"Failed to bytecode-compile Python file in: {}",
site_packages.simplified_display()
)
})?;
}
let s = if files == 1 { "" } else { "s" };
writeln!(
printer.stderr(),

View file

@ -2948,7 +2948,7 @@ fn compile_invalid_pyc_invalidation_mode() -> Result<()> {
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
error: Failed to bytecode compile [SITE-PACKAGES]
error: Failed to bytecode-compile Python file in: [SITE-PACKAGES]
Caused by: Python process stderr:
Invalid value for PYC_INVALIDATION_MODE "bogus", valid are "TIMESTAMP", "CHECKED_HASH", "UNCHECKED_HASH":
Caused by: Bytecode compilation failed, expected "[SITE-PACKAGES]/[FIRST-FILE]", received: ""