[red-knot] Change venv discovery (#17099)

## Summary

Rewrites the virtual env discovery to:

* Only use of `System` APIs, this ensures that the discovery will also
work when using a memory file system (testing or WASM)
* Don't traverse ancestor directories. We're not convinced that this is
necessary. Let's wait until someone shows us a use case where it is
needed
* Start from the project root and not from the current working
directory. This ensures that Red Knot picks up the right venv even when
using `knot --project ../other-dir`

## Test Plan

Existing tests, @ntBre tested that the `file_watching` tests no longer
pick up his virtual env in a parent directory
This commit is contained in:
Micha Reiser 2025-03-31 19:39:05 +02:00 committed by GitHub
parent 4a6fa5fc27
commit a1535fbdbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 20 deletions

View file

@ -121,7 +121,7 @@ impl Options {
.ok()
.map(PythonPath::from_virtual_env_var)
})
.unwrap_or(PythonPath::Discover),
.unwrap_or_else(|| PythonPath::Discover(project_root.to_path_buf())),
}
}

View file

@ -133,20 +133,13 @@ pub(crate) fn search_paths(db: &dyn Db) -> SearchPathIterator {
Program::get(db).search_paths(db).iter(db)
}
/// Searches for a `.venv` directory in the current or any parent directory
fn virtual_env_from_working_dir() -> Option<SystemPathBuf> {
let current_dir = std::env::current_dir().ok()?;
/// Searches for a `.venv` directory in `project_root` that contains a `pyvenv.cfg` file.
fn discover_venv_in(system: &dyn System, project_root: &SystemPath) -> Option<SystemPathBuf> {
let virtual_env_directory = project_root.join(".venv");
for dir in current_dir.ancestors() {
let dot_venv = dir.join(".venv");
if dot_venv.is_dir() {
if !dot_venv.join("pyvenv.cfg").is_file() {
return None;
}
return SystemPathBuf::from_path_buf(dot_venv).ok();
}
}
None
system
.is_file(&virtual_env_directory.join("pyvenv.cfg"))
.then_some(virtual_env_directory)
}
#[derive(Debug, PartialEq, Eq)]
@ -251,15 +244,15 @@ impl SearchPaths {
.and_then(|venv| venv.site_packages_directories(system))?
}
PythonPath::Discover => {
tracing::debug!("Discovering virtual environment");
let virtual_env_path = virtual_env_from_working_dir();
PythonPath::Discover(root) => {
tracing::debug!("Discovering virtual environment in `{root}`");
let virtual_env_path = discover_venv_in(db.system(), root);
if let Some(virtual_env_path) = virtual_env_path {
tracing::debug!("Found `.venv` folder at '{}'", virtual_env_path);
tracing::debug!("Found `.venv` folder at `{}`", virtual_env_path);
let handle_invalid_virtual_env = |error: SitePackagesDiscoveryError| {
tracing::debug!(
"Ignoring automatically detected virtual environment at '{}': {}",
"Ignoring automatically detected virtual environment at `{}`: {}",
virtual_env_path,
error
);

View file

@ -145,7 +145,8 @@ pub enum PythonPath {
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
SysPrefix(SystemPathBuf, SysPrefixPathOrigin),
Discover,
/// Tries to discover a virtual environment in the given path.
Discover(SystemPathBuf),
/// Resolved site packages paths.
///