[ty] Invalidate dependency metadata when environments change (#29126)

`Project::dependency_metadata` resolves Python environments inside a
Salsa query without tracking those inputs. This can leave
invalid-environment, missing-environment, and environment-mismatch
diagnostics stale after an environment changes.

Store the selected environment's resolution result in `ProgramSettings`
and track uv's environment directory through Salsa. Resolve uv's
canonical path whenever the query runs so it observes symlink changes
even when uv metadata is unchanged.

After a failed selection, retain the last usable environment for
watching and recovery, and preserve the checking program's search paths.
Dependency metadata reports the current environment error.

Fixes astral-sh/ty#4604.
This commit is contained in:
Zsolt Dollenstein authored and GitHub committed 2026-10-07 14:27:03 +00:00
1 parent 35f1b2a242
commit e0da489892
11 files changed
+436 -53

No files matched your search

+3 -1
View File
@@ -4,7 +4,9 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use dashmap::mapref::entry::Entry;
pub use directory::{DirectoryListing, DirectoryListingError, directory_listing};
pub use directory::{
DirectoryListing, DirectoryListingError, directory_listing, system_path_to_directory,
};
pub use file_root::{FileRoot, FileRootKind};
pub use path::FilePath;
use ruff_notebook::{Notebook, NotebookError};
+5 -1
View File
@@ -98,7 +98,10 @@ impl ProjectDatabase {
let configuration_paths = ConfigurationPaths::from_metadata(metadata);
// The initial uv metadata request may have failed before a workspace could be discovered.
let uv_enabled = metadata.use_uv().workspace_discovery_enabled();
let virtual_environment = project.program_settings(self).virtual_environment.clone();
let virtual_environment = project
.program_settings(self)
.virtual_environment()
.map(SystemPath::to_path_buf);
let python_path = metadata.configured_python_path(self.system()).or_else(|| {
PythonEnvironment::virtual_environment_candidate(Some(&project_root), self.system())
.map(|(path, _)| SystemPath::absolute(path, self.system().current_directory()))
@@ -423,6 +426,7 @@ impl ProjectDatabase {
}
Err(error) => {
tracing::error!("Failed to resolve program settings: {error}");
project.update_environment_error(self, &error);
}
}
}
+50 -19
View File
@@ -3,7 +3,7 @@
reason = "Prefer System trait methods over std methods in ty crates"
)]
use crate::glob::{GlobFilterCheckMode, IncludeResult};
use crate::metadata::options::OptionDiagnostic;
use crate::metadata::options::{OptionDiagnostic, ToProgramSettingsError};
use crate::parallel::ParallelIteratorExt;
use crate::script::Script;
use crate::walk::{ProjectFilesFilter, ProjectFilesWalker};
@@ -18,7 +18,7 @@ use rayon::prelude::*;
use ruff_db::diagnostic::{
Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic, SubDiagnosticSeverity,
};
use ruff_db::files::{File, system_path_to_file};
use ruff_db::files::{File, system_path_to_directory, system_path_to_file};
use ruff_db::parsed::parsed_module;
use ruff_db::system::{SystemPath, SystemPathBuf, deduplicate_nested_paths};
use rustc_hash::FxHashSet;
@@ -30,7 +30,7 @@ use std::iter::FusedIterator;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::sync::Arc;
use ty_python_core::ProgramFile;
use ty_python_core::program::{FallibleStrategy, Program, ProgramSettings};
use ty_python_core::program::{FallibleStrategy, Program, ProgramSettings, PythonEnvironmentError};
pub use ty_python_semantic::Db as SemanticDb;
use ty_python_semantic::dependency::{DependencyMetadata, DependencyProjectKind};
use ty_python_semantic::lint::RuleSelection;
@@ -278,7 +278,7 @@ impl Project {
Program::from_settings(db, self.program_settings(db))
}
/// Extract dependency information once per metadata update. Unrelated project settings and
/// Extract dependency information for the resolved Python environment. Unrelated settings and
/// source ranges do not invalidate import inference when the extracted information is equal.
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn dependency_metadata(
@@ -296,28 +296,41 @@ impl Project {
let environment = workspace
.environment()
.ok_or(DependencyMetadataError::MissingEnvironment)?;
let environment = db
.system()
.canonicalize_path(environment)
.map_err(|error| DependencyMetadataError::InvalidEnvironment {
path: environment.to_path_buf(),
message: error.to_string().into(),
})?;
let selected_environment = metadata
.to_merged_options()
.python_environment(db.system())
.map_err(|error| {
DependencyMetadataError::EnvironmentResolution(error.to_string().into())
})?
// Track the path before resolving it, so a synced change can invalidate the result.
if let Ok(directory) = system_path_to_directory(db, environment) {
let _ = directory.revision(db);
}
let canonical_environment =
db.system()
.canonicalize_path(environment)
.map_err(|error| {
// If `.venv` points to `env`, deleting `env` makes this fail before we read
// the target's directory status below. Salsa then drops that dependency,
// so recreating `env` and syncing only its path won't invalidate the cached
// error. Retry on the next revision so dependency checks can recover.
db.report_untracked_read();
DependencyMetadataError::InvalidEnvironment {
path: environment.to_path_buf(),
message: error.to_string().into(),
}
})?;
// Changes may be reported for the canonical target instead of the symlink.
let _ = system_path_to_directory(db, &canonical_environment);
let program_settings = self.program_settings(db);
let selected_environment = program_settings
.python_environment
.as_ref()
.map_err(|error| DependencyMetadataError::EnvironmentResolution(error.message.clone()))?
.as_ref()
.ok_or(DependencyMetadataError::MissingSelectedEnvironment)?;
// An explicit Python environment can override uv's selection. Its installed modules may
// belong to different distributions, so uv's ownership map cannot describe those imports.
if selected_environment.sys_prefix().as_std_path() != environment.as_std_path() {
if selected_environment.sys_prefix().as_std_path() != canonical_environment.as_std_path() {
return Err(DependencyMetadataError::EnvironmentMismatch {
selected: selected_environment.sys_prefix().to_path_buf(),
selected_origin: selected_environment.origin().to_string().into(),
uv: environment,
uv: canonical_environment,
});
}
@@ -333,6 +346,23 @@ impl Project {
}
}
/// Update or clear the environment error while retaining the last working search paths.
fn update_environment_error(self, db: &mut dyn Db, error: &ToProgramSettingsError) {
let mut settings = self.program_settings(db).clone();
let last_usable = match settings.python_environment {
Ok(environment) => environment,
Err(error) => error.last_usable,
};
settings.python_environment = match error {
ToProgramSettingsError::PythonEnvironment(error) => Err(PythonEnvironmentError {
message: error.to_string().into(),
last_usable,
}),
_ => Ok(last_usable),
};
self.update_program(db, settings);
}
pub fn root(self, db: &dyn Db) -> &SystemPath {
self.metadata(db).root()
}
@@ -402,6 +432,7 @@ impl Project {
"Failed to convert metadata to program settings, \
continuing without applying them: {error}"
);
self.update_environment_error(db, &error);
Vec::new()
}
};
+313
View File
@@ -654,11 +654,16 @@ mod tests {
use anyhow::{Context, anyhow};
use insta::assert_ron_snapshot;
use ruff_db::Db as _;
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File;
#[cfg(unix)]
use ruff_db::system::{OsSystem, System, SystemPath, WritableSystem};
use ruff_db::system::{SystemPathBuf, TestSystem};
use ruff_db::testing::assert_function_query_was_not_run_by_name;
use ruff_python_ast::PythonVersion;
use ruff_ranged_value::ValueSource;
use ty_python_core::program::UseDefaultStrategy;
use ty_python_semantic::PythonVersionSource;
use crate::db::{ProjectDatabase, testing::TestDb};
@@ -666,6 +671,8 @@ mod tests {
Options, python_version::SupportedPythonVersion, uv::UvMetadata, value::RelativePathBuf,
};
use crate::uv::{DependencyMetadataError, UvWorkspace};
#[cfg(unix)]
use crate::watch::{ChangeEvent, CreatedKind, DeletedKind};
use crate::{Db as _, ProjectMetadata, ProjectMetadataError};
/// Without a `pyproject.toml` or `ty.toml`, the selected directory is the project root.
@@ -1155,6 +1162,312 @@ unclosed table, expected `]`
Ok(())
}
#[test]
fn dependency_metadata_tracks_environment_changes() -> anyhow::Result<()> {
let system = TestSystem::default();
let root = SystemPathBuf::from(if cfg!(windows) { "C:/app" } else { "/app" });
let uv_environment = root.join("uv-venv");
let selected_environment = root.join("selected-venv");
system
.memory_file_system()
.create_directory_all(&uv_environment)?;
let input = serde_json::json!({
"schema": {"version": "preview"},
"workspace_root": root,
"environment": {"root": uv_environment},
});
let mut metadata = ProjectMetadata::new("app", root);
metadata.apply_uv_workspace(
&system,
UvWorkspace {
metadata: Some(UvMetadata::from_metadata(
&serde_json::to_vec(&input)?,
&system,
)?),
error: None,
},
)?;
metadata.set_override_options(Options::from_toml_str(
&format!("[environment]\npython = '{selected_environment}'"),
ValueSource::Cli,
)?);
let mut db = ProjectDatabase::use_defaults(metadata, system.clone());
let project = db.project();
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentResolution(_))
);
// Creating the selected environment refreshes program settings without changing metadata.
system.memory_file_system().write_file_all(
selected_environment.join("pyvenv.cfg"),
"home = /missing\nversion = 3.13.0\ninclude-system-site-packages = false",
)?;
system
.memory_file_system()
.create_directory_all(selected_environment.join(if cfg!(windows) {
"Lib/site-packages"
} else {
"lib/python3.13/site-packages"
}))?;
let (settings, _) = project
.metadata(&db)
.to_merged_options()
.to_program_settings(&system, db.vendored(), &UseDefaultStrategy)?;
project.update_program(&mut db, settings);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentMismatch { selected, .. })
if selected == &selected_environment
);
// Directory availability is tracked independently of the resolved program settings.
system
.memory_file_system()
.remove_directory(&uv_environment)?;
File::sync_path(&mut db, &uv_environment);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::InvalidEnvironment { .. })
);
system
.memory_file_system()
.create_directory_all(&uv_environment)?;
File::sync_path(&mut db, &uv_environment);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentMismatch { .. })
);
Ok(())
}
#[test]
#[cfg(unix)]
fn dependency_metadata_tracks_environment_symlink() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let root = SystemPath::from_std_path(temp.path()).context("non-UTF-8 temporary path")?;
let system = OsSystem::new(root);
let root = system.canonicalize_path(root)?;
let environment = root.join("environment");
let link = root.join(".venv");
system.create_directory_all(&environment.join("lib/python3.13/site-packages"))?;
system.write_file(
&environment.join("pyvenv.cfg"),
"home = /missing\nversion = 3.13.0\ninclude-system-site-packages = false",
)?;
std::os::unix::fs::symlink(&environment, &link)?;
let input = serde_json::json!({
"schema": {"version": "preview"},
"workspace_root": root,
"environment": {"root": link},
"members": [{"id": "app", "name": "app", "path": root}],
"resolution": {"app": {"kind": "package", "name": "app", "dependencies": []}},
});
let mut metadata = ProjectMetadata::new("app", root);
metadata.apply_uv_workspace(
&system,
UvWorkspace {
metadata: Some(UvMetadata::from_metadata(
&serde_json::to_vec(&input)?,
&system,
)?),
error: None,
},
)?;
let mut db = ProjectDatabase::fallible(metadata, system.clone())?;
let project = db.project();
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
// The selected prefix is canonical, but uv's original path must remain available too.
std::fs::remove_file(&link)?;
File::sync_path(&mut db, &link);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::InvalidEnvironment { .. })
);
std::os::unix::fs::symlink(&environment, &link)?;
File::sync_path(&mut db, &link);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
// Syncing only the target must invalidate the query without refreshing program settings.
std::fs::remove_dir_all(&environment)?;
File::sync_path(&mut db, &environment);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::InvalidEnvironment { .. })
);
system.create_directory_all(&environment)?;
File::sync_path(&mut db, &environment);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
Ok(())
}
#[test]
#[cfg(unix)]
fn dependency_metadata_tracks_failed_selected_environment_refresh() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let root = SystemPath::from_std_path(temp.path()).context("non-UTF-8 temporary path")?;
let system = OsSystem::new(root);
let root = system.canonicalize_path(root)?;
let environment = root.join("environment");
let uv_link = root.join(".venv");
let selected_link = root.join("selected");
system.create_directory_all(&environment.join("lib/python3.13/site-packages"))?;
system.write_file(
&environment.join("pyvenv.cfg"),
"home = /missing\nversion = 3.13.0\ninclude-system-site-packages = false",
)?;
std::os::unix::fs::symlink(&environment, &uv_link)?;
std::os::unix::fs::symlink(&environment, &selected_link)?;
let input = serde_json::json!({
"schema": {"version": "preview"},
"workspace_root": root,
"environment": {"root": uv_link},
"members": [{"id": "app", "name": "app", "path": root}],
"resolution": {"app": {"kind": "package", "name": "app", "dependencies": []}},
});
let mut metadata = ProjectMetadata::new("app", root.clone());
metadata.apply_uv_workspace(
&system,
UvWorkspace {
metadata: Some(UvMetadata::from_metadata(
&serde_json::to_vec(&input)?,
&system,
)?),
error: None,
},
)?;
metadata.set_override_options(Options::from_toml_str(
&format!("[environment]\npython = '{selected_link}'"),
ValueSource::Cli,
)?);
let mut db = ProjectDatabase::fallible(metadata, system)?;
let project = db.project();
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
let search_paths = project.program_settings(&db).search_paths.clone();
// Repeated failures must retain the checking program and the environment used for watching.
std::fs::remove_file(&selected_link)?;
for _ in 0..2 {
let workspace = project.metadata(&db).uv_workspace().clone();
project.rediscover(&mut db, &root, workspace)?;
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentResolution(message))
if message.contains(selected_link.as_str())
);
assert_eq!(project.program_settings(&db).search_paths, search_paths);
assert_eq!(
project.program_settings(&db).virtual_environment(),
Some(environment.as_path()),
);
}
std::os::unix::fs::symlink(&environment, &selected_link)?;
// A different settings error must clear the stale environment error, too.
let system = OsSystem::new(&root);
system.create_directory_all(&root.join("broken-typeshed/stdlib"))?;
system.write_file(
&root.join("pyproject.toml"),
"[tool.ty.environment]\ntypeshed = 'broken-typeshed'\n",
)?;
let workspace = project.metadata(&db).uv_workspace().clone();
project.rediscover(&mut db, &root, workspace)?;
assert_matches!(
project
.metadata(&db)
.to_merged_options()
.to_program_settings(db.system(), db.vendored(), &super::FallibleStrategy),
Err(super::ToProgramSettingsError::SearchPaths(_))
);
assert_eq!(project.program_settings(&db).search_paths, search_paths);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
std::fs::remove_file(root.join("pyproject.toml"))?;
let workspace = project.metadata(&db).uv_workspace().clone();
project.rediscover(&mut db, &root, workspace)?;
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
std::fs::remove_file(&selected_link)?;
db.apply_changes(&[ChangeEvent::Deleted {
path: selected_link.clone(),
kind: DeletedKind::File,
}]);
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentResolution(_))
);
std::os::unix::fs::symlink(&environment, &selected_link)?;
db.apply_changes(&[ChangeEvent::Created {
path: selected_link,
kind: CreatedKind::Any,
}]);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
Ok(())
}
#[test]
#[cfg(unix)]
fn dependency_metadata_rechecks_uv_symlink_after_program_change() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let root = SystemPath::from_std_path(temp.path()).context("non-UTF-8 temporary path")?;
let system = OsSystem::new(root);
let root = system.canonicalize_path(root)?;
let old_environment = root.join("old");
let selected_environment = root.join("selected");
let uv_link = root.join(".venv");
system.create_directory_all(&old_environment)?;
system.create_directory_all(&selected_environment.join("lib/python3.13/site-packages"))?;
system.write_file(
&selected_environment.join("pyvenv.cfg"),
"home = /missing\nversion = 3.13.0\ninclude-system-site-packages = false",
)?;
std::os::unix::fs::symlink(&old_environment, &uv_link)?;
let input = serde_json::json!({
"schema": {"version": "preview"},
"workspace_root": root,
"environment": {"root": uv_link},
"members": [{"id": "app", "name": "app", "path": root}],
"resolution": {"app": {"kind": "package", "name": "app", "dependencies": []}},
});
let input = serde_json::to_vec(&input)?;
let mut metadata = ProjectMetadata::new("app", root);
metadata.apply_uv_workspace(
&system,
UvWorkspace {
metadata: Some(UvMetadata::from_metadata(&input, &system)?),
error: None,
},
)?;
metadata.set_override_options(Options::from_toml_str(
&format!("[environment]\npython = '{selected_environment}'"),
ValueSource::Cli,
)?);
let mut db = ProjectDatabase::fallible(metadata, system)?;
let project = db.project();
assert_matches!(
project.dependency_metadata(&db),
Err(DependencyMetadataError::EnvironmentMismatch { .. })
);
std::fs::remove_file(&uv_link)?;
std::os::unix::fs::symlink(&selected_environment, &uv_link)?;
// An unrelated program change invalidates the query without refreshing uv metadata.
let mut settings = project.program_settings(&db).clone();
settings.python_version.version = PythonVersion::PY312;
project.update_program(&mut db, settings);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));
Ok(())
}
/// A uv refresh failure is reported without a second warning about missing dependency metadata.
#[test]
fn uv_refresh_error_takes_precedence_over_dependency_error() -> anyhow::Result<()> {
+13 -4
View File
@@ -35,7 +35,7 @@ use ty_module_resolver::{
ModuleGlobSet, ModuleGlobSetBuilder, SearchPathSettings, SearchPathSettingsError, SearchPaths,
};
use ty_python_core::platform::PythonPlatform;
use ty_python_core::program::{MisconfigurationStrategy, ProgramSettings};
use ty_python_core::program::{MisconfigurationStrategy, ProgramSettings, PythonEnvironmentError};
use ty_python_semantic::lint::{Level, LintSource, RuleSelection};
use ty_python_semantic::{
AnalysisSettings, PythonEnvironment, PythonVersionFileSource, PythonVersionSource,
@@ -216,6 +216,14 @@ impl Options {
configured => configured.map_err(ToProgramSettingsError::PythonEnvironment),
};
let python_environment_error = match &python_environment {
Err(ToProgramSettingsError::PythonEnvironment(error)) => Some(PythonEnvironmentError {
message: error.to_string().into(),
last_usable: None,
}),
_ => None,
};
// If in safe-mode, fallback to None if this fails instead of erroring.
let python_environment = strategy
.fallback_opt(python_environment, |_| {
@@ -306,9 +314,10 @@ impl Options {
python_version,
python_platform,
search_paths,
virtual_environment: python_environment
.filter(PythonEnvironment::is_virtual)
.map(|environment| environment.sys_prefix().to_path_buf()),
python_environment: match python_environment_error {
Some(error) => Err(error),
None => Ok(python_environment),
},
},
diagnostics,
))
@@ -252,8 +252,7 @@ pub fn watch_paths(db: &dyn Db, project: Project) -> WatchPaths {
.map(UvMetadata::workspace_root);
let virtual_environment = project
.program_settings(db)
.virtual_environment
.as_deref()
.virtual_environment()
.filter(|environment| !environment.starts_with(project_path));
// Watch the workspace and project roots and any paths provided by the user on the CLI.
+1 -1
View File
@@ -166,7 +166,7 @@ pub(crate) mod tests {
.context("Failed to write test files")?;
let program_settings = ProgramSettings {
virtual_environment: None,
python_environment: Ok(None),
python_version: PythonVersionWithSource {
version: self.python_version,
source: PythonVersionSource::default(),
+38 -9
View File
@@ -1,11 +1,13 @@
use crate::{Db, platform::PythonPlatform};
use std::fmt;
use ruff_db::files::File;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_db::system::SystemPath;
use ruff_db::vendored::VendoredFileSystem;
use ruff_python_ast::PythonVersion;
use ty_module_resolver::{ResolverEnvironment, SearchPaths};
use ty_site_packages::PythonVersionWithSource;
use ty_site_packages::{PythonEnvironment, PythonVersionWithSource};
use crate::ProgramFile;
@@ -30,7 +32,7 @@ impl<'db> Program<'db> {
python_version,
python_platform,
search_paths,
virtual_environment: _,
python_environment: _,
} = settings;
let resolver_environment =
@@ -60,11 +62,9 @@ pub struct ProgramSettings {
pub python_version: PythonVersionWithSource,
pub python_platform: PythonPlatform,
pub search_paths: SearchPaths,
/// The root of the resolved virtual environment, if any. File watchers use this to observe
/// `pyvenv.cfg` and directory changes without resolving the environment again. System Python
/// installations are very unlikely to be deleted and recreated, so we exclude them to avoid
/// recursively watching a system prefix such as `/usr`.
pub virtual_environment: Option<SystemPathBuf>,
/// The resolved Python environment. Queries must use this instead of resolving the environment
/// again, so that changes invalidate their cached results.
pub python_environment: Result<Option<PythonEnvironment>, PythonEnvironmentError>,
}
impl ProgramSettings {
@@ -73,7 +73,36 @@ impl ProgramSettings {
python_version: PythonVersionWithSource::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPaths::empty(vendored),
virtual_environment: None,
python_environment: Ok(None),
}
}
/// The root of the resolved virtual environment, for watching `pyvenv.cfg` and directory changes.
/// System installations are unlikely to be recreated, and watching a prefix such as `/usr`
/// recursively would be expensive.
pub fn virtual_environment(&self) -> Option<&SystemPath> {
let environment = match &self.python_environment {
Ok(environment) => environment,
Err(error) => &error.last_usable,
};
environment
.as_ref()
.filter(|environment| environment.is_virtual())
.map(|environment| &**environment.sys_prefix())
}
}
/// A failed environment selection that retains the previous environment for watching and recovery.
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
pub struct PythonEnvironmentError {
pub message: Box<str>,
pub last_usable: Option<PythonEnvironment>,
}
impl fmt::Display for PythonEnvironmentError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
impl std::error::Error for PythonEnvironmentError {}
+1 -3
View File
@@ -318,9 +318,7 @@ pub(crate) mod tests {
};
let program_settings = ProgramSettings {
virtual_environment: self
.third_party_packages
.then(|| SystemPathBuf::from("/.venv")),
python_environment: Ok(None),
python_version: PythonVersionWithSource {
version: self.python_version,
source: PythonVersionSource::default(),
+10 -10
View File
@@ -270,7 +270,7 @@ impl PartialEq<&[SystemPathBuf]> for SitePackagesPaths {
}
}
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub enum PythonEnvironment {
Virtual(VirtualEnvironment),
System(SystemEnvironment),
@@ -493,7 +493,7 @@ impl std::fmt::Display for InstallationDir {
///
/// We only need to distinguish cases that change the on-disk layout.
/// Everything else can be treated like CPython.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, get_size2::GetSize)]
pub(crate) enum PythonImplementation {
CPython,
PyPy,
@@ -520,7 +520,7 @@ impl PythonImplementation {
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, get_size2::GetSize)]
struct PythonInterpreterLayout {
version: Option<PythonVersion>,
implementation: PythonImplementation,
@@ -649,7 +649,7 @@ impl PythonInterpreterLayout {
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, get_size2::GetSize)]
enum PythonBuildVariant {
#[default]
Unknown,
@@ -686,7 +686,7 @@ impl PythonBuildVariant {
/// Most of this information is derived from the virtual environment's `pyvenv.cfg` file.
/// The format of this file is not defined anywhere, and exactly which keys are present
/// depends on the tool that was used to create the virtual environment.
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub struct VirtualEnvironment {
root_path: SysPrefixPath,
base_executable_home_path: Option<PythonHomePath>,
@@ -1156,7 +1156,7 @@ struct RawPyvenvCfg<'s> {
///
/// This environment may or may not be one that is managed by the operating system itself, e.g.,
/// this captures both Homebrew-installed Python versions and the bundled macOS Python installation.
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub struct SystemEnvironment {
path: PythonEnvironmentPath,
}
@@ -1861,7 +1861,7 @@ fn real_stdlib_directory_from_sys_prefix(
/// `/opt/homebrew/lib/python3.X/site-packages`.
///
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
pub struct SysPrefixPath {
inner: SystemPathBuf,
origin: SysPrefixPathOrigin,
@@ -1887,7 +1887,7 @@ fn sys_prefix_from_executable_path(path: &SystemPath) -> Option<&SystemPath> {
}
/// A selected Python environment path resolved to its `sys.prefix`.
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
enum PythonEnvironmentPath {
Prefix(SysPrefixPath),
Executable {
@@ -2126,7 +2126,7 @@ impl Deref for SysPrefixPath {
}
/// Enumeration of sources a `sys.prefix` path can come from.
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
pub enum SysPrefixPathOrigin {
/// The `sys.prefix` path came from a configuration file setting: `pyproject.toml` or `ty.toml`
ConfigFileSetting(Arc<SystemPathBuf>, Option<TextRange>),
@@ -2258,7 +2258,7 @@ impl std::fmt::Display for SysPrefixPathOrigin {
///
/// [`PYTHONHOME`]: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
/// [the original PEP adding the `venv` module]: https://peps.python.org/pep-0405/
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
struct PythonHomePath(SystemPathBuf);
impl PythonHomePath {
+1 -3
View File
@@ -282,9 +282,7 @@ fn run_test(
.collect();
let settings = ProgramSettings {
virtual_environment: python_environment
.filter(PythonEnvironment::is_virtual)
.map(|environment| environment.sys_prefix().to_path_buf()),
python_environment: Ok(python_environment),
python_version: PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::Cli,