[ty] Move search path resolution to Options::to_program_settings (#18937)

This commit is contained in:
Micha Reiser 2025-06-25 18:00:38 +02:00 committed by GitHub
parent 8b22992988
commit 5d546c600a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 481 additions and 549 deletions

View file

@ -183,15 +183,16 @@ pub(crate) mod tests {
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource {
python_version: PythonVersionWithSource {
version: self.python_version,
source: PythonVersionSource::default(),
}),
},
python_platform: self.python_platform,
search_paths: SearchPathSettings::new(vec![src_root]),
search_paths: SearchPathSettings::new(vec![src_root])
.to_search_paths(db.system(), db.vendored())
.context("Invalid search path settings")?,
},
)
.context("Failed to configure Program settings")?;
);
Ok(db)
}

View file

@ -6,7 +6,10 @@ use crate::lint::{LintRegistry, LintRegistryBuilder};
use crate::suppression::{INVALID_IGNORE_COMMENT, UNKNOWN_RULE, UNUSED_IGNORE_COMMENT};
pub use db::Db;
pub use module_name::ModuleName;
pub use module_resolver::{KnownModule, Module, resolve_module, system_module_search_paths};
pub use module_resolver::{
KnownModule, Module, SearchPathValidationError, SearchPaths, resolve_module,
system_module_search_paths,
};
pub use program::{
Program, ProgramSettings, PythonPath, PythonVersionFileSource, PythonVersionSource,
PythonVersionWithSource, SearchPathSettings,

View file

@ -1,8 +1,10 @@
use std::iter::FusedIterator;
pub use module::{KnownModule, Module};
pub use path::SearchPathValidationError;
pub use resolver::SearchPaths;
pub(crate) use resolver::file_to_module;
pub use resolver::resolve_module;
pub(crate) use resolver::{SearchPaths, file_to_module};
use ruff_db::system::SystemPath;
use crate::Db;

View file

@ -9,7 +9,6 @@ use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
use super::typeshed::{TypeshedVersionsParseError, TypeshedVersionsQueryResult, typeshed_versions};
use crate::db::Db;
use crate::module_name::ModuleName;
use crate::module_resolver::resolver::ResolverContext;
use crate::site_packages::SitePackagesDiscoveryError;
@ -325,62 +324,37 @@ fn query_stdlib_version(
/// If validation fails for a search path derived from the user settings,
/// a message must be displayed to the user,
/// as type checking cannot be done reliably in these circumstances.
#[derive(Debug)]
pub(crate) enum SearchPathValidationError {
#[derive(Debug, thiserror::Error)]
pub enum SearchPathValidationError {
/// The path provided by the user was not a directory
#[error("{0} does not point to a directory")]
NotADirectory(SystemPathBuf),
/// The path provided by the user is a directory,
/// but no `stdlib/` subdirectory exists.
/// (This is only relevant for stdlib search paths.)
#[error("The directory at {0} has no `stdlib/` subdirectory")]
NoStdlibSubdirectory(SystemPathBuf),
/// The typeshed path provided by the user is a directory,
/// but `stdlib/VERSIONS` could not be read.
/// (This is only relevant for stdlib search paths.)
#[error("Failed to read the custom typeshed versions file '{path}'")]
FailedToReadVersionsFile {
path: SystemPathBuf,
#[source]
error: std::io::Error,
},
/// The path provided by the user is a directory,
/// and a `stdlib/VERSIONS` file exists, but it fails to parse.
/// (This is only relevant for stdlib search paths.)
#[error(transparent)]
VersionsParseError(TypeshedVersionsParseError),
/// Failed to discover the site-packages for the configured virtual environment.
SitePackagesDiscovery(SitePackagesDiscoveryError),
}
impl fmt::Display for SearchPathValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotADirectory(path) => write!(f, "{path} does not point to a directory"),
Self::NoStdlibSubdirectory(path) => {
write!(f, "The directory at {path} has no `stdlib/` subdirectory")
}
Self::FailedToReadVersionsFile { path, error } => {
write!(
f,
"Failed to read the custom typeshed versions file '{path}': {error}"
)
}
Self::VersionsParseError(underlying_error) => underlying_error.fmt(f),
SearchPathValidationError::SitePackagesDiscovery(error) => {
write!(f, "Failed to discover the site-packages directory: {error}")
}
}
}
}
impl std::error::Error for SearchPathValidationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
if let Self::VersionsParseError(underlying_error) = self {
Some(underlying_error)
} else {
None
}
}
#[error("Failed to discover the site-packages directory")]
SitePackagesDiscovery(#[source] SitePackagesDiscoveryError),
}
impl From<TypeshedVersionsParseError> for SearchPathValidationError {
@ -459,8 +433,10 @@ impl SearchPath {
}
/// Create a new standard-library search path pointing to a custom directory on disk
pub(crate) fn custom_stdlib(db: &dyn Db, typeshed: &SystemPath) -> SearchPathResult<Self> {
let system = db.system();
pub(crate) fn custom_stdlib(
system: &dyn System,
typeshed: &SystemPath,
) -> SearchPathResult<Self> {
if !system.is_directory(typeshed) {
return Err(SearchPathValidationError::NotADirectory(
typeshed.to_path_buf(),
@ -528,6 +504,10 @@ impl SearchPath {
)
}
pub(crate) fn is_first_party(&self) -> bool {
matches!(&*self.0, SearchPathInner::FirstParty(_))
}
fn is_valid_extension(&self, extension: &str) -> bool {
if self.is_standard_library() {
extension == "pyi"
@ -707,7 +687,7 @@ mod tests {
.build();
assert_eq!(
SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap())
.unwrap()
.to_module_path()
.with_py_extension(),
@ -715,7 +695,7 @@ mod tests {
);
assert_eq!(
&SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
&SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap())
.unwrap()
.join("foo")
.with_pyi_extension(),
@ -826,7 +806,7 @@ mod tests {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_mocked_typeshed(MockedTypeshed::default())
.build();
SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap())
.unwrap()
.to_module_path()
.push("bar.py");
@ -838,7 +818,7 @@ mod tests {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_mocked_typeshed(MockedTypeshed::default())
.build();
SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap())
.unwrap()
.to_module_path()
.push("bar.rs");
@ -870,7 +850,7 @@ mod tests {
.with_mocked_typeshed(MockedTypeshed::default())
.build();
let root = SearchPath::custom_stdlib(&db, stdlib.parent().unwrap()).unwrap();
let root = SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap()).unwrap();
// Must have a `.pyi` extension or no extension:
let bad_absolute_path = SystemPath::new("foo/stdlib/x.py");
@ -918,7 +898,7 @@ mod tests {
.with_mocked_typeshed(typeshed)
.with_python_version(python_version)
.build();
let stdlib = SearchPath::custom_stdlib(&db, stdlib.parent().unwrap()).unwrap();
let stdlib = SearchPath::custom_stdlib(db.system(), stdlib.parent().unwrap()).unwrap();
(db, stdlib)
}

View file

@ -142,9 +142,9 @@ pub(crate) fn search_paths(db: &dyn Db) -> SearchPathIterator {
Program::get(db).search_paths(db).iter(db)
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchPaths {
/// Search paths that have been statically determined purely from reading Ruff's configuration settings.
/// Search paths that have been statically determined purely from reading ty's configuration settings.
/// These shouldn't ever change unless the config settings themselves change.
static_paths: Vec<SearchPath>,
@ -174,8 +174,9 @@ impl SearchPaths {
///
/// [module resolution order]: https://typing.python.org/en/latest/spec/distributing.html#import-resolution-ordering
pub(crate) fn from_settings(
db: &dyn Db,
settings: &SearchPathSettings,
system: &dyn System,
vendored: &VendoredFileSystem,
) -> Result<Self, SearchPathValidationError> {
fn canonicalize(path: &SystemPath, system: &dyn System) -> SystemPathBuf {
system
@ -190,14 +191,10 @@ impl SearchPaths {
python_path,
} = settings;
let system = db.system();
let files = db.files();
let mut static_paths = vec![];
for path in extra_paths {
let path = canonicalize(path, system);
files.try_add_root(db.upcast(), &path, FileRootKind::LibrarySearchPath);
tracing::debug!("Adding extra search-path '{path}'");
static_paths.push(SearchPath::extra(system, path)?);
@ -212,8 +209,6 @@ impl SearchPaths {
let typeshed = canonicalize(typeshed, system);
tracing::debug!("Adding custom-stdlib search path '{typeshed}'");
files.try_add_root(db.upcast(), &typeshed, FileRootKind::LibrarySearchPath);
let versions_path = typeshed.join("stdlib/VERSIONS");
let versions_content = system.read_to_string(&versions_path).map_err(|error| {
@ -225,13 +220,13 @@ impl SearchPaths {
let parsed: TypeshedVersions = versions_content.parse()?;
let search_path = SearchPath::custom_stdlib(db, &typeshed)?;
let search_path = SearchPath::custom_stdlib(system, &typeshed)?;
(parsed, search_path)
} else {
tracing::debug!("Using vendored stdlib");
(
vendored_typeshed_versions(db),
vendored_typeshed_versions(vendored),
SearchPath::vendored_stdlib(),
)
};
@ -282,7 +277,6 @@ impl SearchPaths {
for path in site_packages_paths {
tracing::debug!("Adding site-packages search path '{path}'");
files.try_add_root(db.upcast(), &path, FileRootKind::LibrarySearchPath);
site_packages.push(SearchPath::site_packages(system, path)?);
}
@ -313,6 +307,17 @@ impl SearchPaths {
})
}
pub(crate) fn try_register_static_roots(&self, db: &dyn Db) {
let files = db.files();
for path in self.static_paths.iter().chain(self.site_packages.iter()) {
if let Some(system_path) = path.as_system_path() {
if !path.is_first_party() {
files.try_add_root(db.upcast(), system_path, FileRootKind::LibrarySearchPath);
}
}
}
}
pub(super) fn iter<'a>(&'a self, db: &'a dyn Db) -> SearchPathIterator<'a> {
SearchPathIterator {
db,
@ -1482,20 +1487,20 @@ mod tests {
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource {
python_version: PythonVersionWithSource {
version: PythonVersion::PY38,
source: PythonVersionSource::default(),
}),
},
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_roots: vec![src.clone()],
custom_typeshed: Some(custom_typeshed),
python_path: PythonPath::KnownSitePackages(vec![site_packages]),
},
..SearchPathSettings::new(vec![src.clone()])
}
.to_search_paths(db.system(), db.vendored())
.expect("Valid search path settings"),
},
)
.context("Invalid program settings")?;
);
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let bar_module = resolve_module(&db, &ModuleName::new_static("bar").unwrap()).unwrap();
@ -2001,20 +2006,19 @@ not_a_directory
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource::default()),
python_version: PythonVersionWithSource::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_roots: vec![SystemPathBuf::from("/src")],
custom_typeshed: None,
python_path: PythonPath::KnownSitePackages(vec![
venv_site_packages,
system_site_packages,
]),
},
..SearchPathSettings::new(vec![SystemPathBuf::from("/src")])
}
.to_search_paths(db.system(), db.vendored())
.expect("Valid search path settings"),
},
)
.expect("Valid program settings");
);
// The editable installs discovered from the `.pth` file in the first `site-packages` directory
// take precedence over the second `site-packages` directory...
@ -2080,17 +2084,13 @@ not_a_directory
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource::default()),
python_version: PythonVersionWithSource::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_roots: vec![src],
custom_typeshed: None,
python_path: PythonPath::KnownSitePackages(vec![]),
},
search_paths: SearchPathSettings::new(vec![src])
.to_search_paths(db.system(), db.vendored())
.expect("valid search path settings"),
},
)
.expect("Valid program settings");
);
// Now try to resolve the module `A` (note the capital `A` instead of `a`).
let a_module_name = ModuleName::new_static("A").unwrap();
@ -2123,17 +2123,16 @@ not_a_directory
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource::default()),
python_version: PythonVersionWithSource::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_roots: vec![project_directory],
custom_typeshed: None,
python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]),
},
..SearchPathSettings::new(vec![project_directory])
}
.to_search_paths(db.system(), db.vendored())
.unwrap(),
},
)
.unwrap();
);
let foo_module_file = File::new(&db, FilePath::System(installed_foo_module));
let module = file_to_module(&db, foo_module_file).unwrap();

View file

@ -1,3 +1,4 @@
use ruff_db::Db;
use ruff_db::system::{
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
};
@ -237,20 +238,20 @@ impl TestCaseBuilder<MockedTypeshed> {
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource {
python_version: PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::default(),
}),
},
python_platform,
search_paths: SearchPathSettings {
extra_paths: vec![],
src_roots: vec![src.clone()],
custom_typeshed: Some(typeshed.clone()),
python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]),
},
..SearchPathSettings::new(vec![src.clone()])
}
.to_search_paths(db.system(), db.vendored())
.expect("valid search path settings"),
},
)
.expect("Valid program settings");
);
TestCase {
db,
@ -298,18 +299,19 @@ impl TestCaseBuilder<VendoredTypeshed> {
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource {
python_version: PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::default(),
}),
},
python_platform,
search_paths: SearchPathSettings {
python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]),
..SearchPathSettings::new(vec![src.clone()])
},
}
.to_search_paths(db.system(), db.vendored())
.expect("valid search path settings"),
},
)
.expect("Valid search path settings");
);
TestCase {
db,

View file

@ -4,6 +4,7 @@ use std::num::{NonZeroU16, NonZeroUsize};
use std::ops::{RangeFrom, RangeInclusive};
use std::str::FromStr;
use ruff_db::vendored::VendoredFileSystem;
use ruff_python_ast::{PythonVersion, PythonVersionDeserializationError};
use rustc_hash::FxHashMap;
@ -11,9 +12,11 @@ use crate::Program;
use crate::db::Db;
use crate::module_name::ModuleName;
pub(in crate::module_resolver) fn vendored_typeshed_versions(db: &dyn Db) -> TypeshedVersions {
pub(in crate::module_resolver) fn vendored_typeshed_versions(
vendored: &VendoredFileSystem,
) -> TypeshedVersions {
TypeshedVersions::from_str(
&db.vendored()
&vendored
.read_to_string("stdlib/VERSIONS")
.expect("The vendored typeshed stubs should contain a VERSIONS file"),
)
@ -25,7 +28,7 @@ pub(crate) fn typeshed_versions(db: &dyn Db) -> &TypeshedVersions {
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) struct TypeshedVersionsParseError {
pub struct TypeshedVersionsParseError {
line_number: Option<NonZeroU16>,
reason: TypeshedVersionsParseErrorKind,
}
@ -71,7 +74,7 @@ pub(crate) enum TypeshedVersionsParseErrorKind {
VersionParseError(#[from] PythonVersionDeserializationError),
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct TypeshedVersions(FxHashMap<ModuleName, PyVersionRange>);
impl TypeshedVersions {
@ -305,11 +308,8 @@ mod tests {
use std::num::{IntErrorKind, NonZeroU16};
use std::path::Path;
use insta::assert_snapshot;
use crate::db::tests::TestDb;
use super::*;
use insta::assert_snapshot;
const TYPESHED_STDLIB_DIR: &str = "stdlib";
@ -329,9 +329,7 @@ mod tests {
#[test]
fn can_parse_vendored_versions_file() {
let db = TestDb::new();
let versions = vendored_typeshed_versions(&db);
let versions = vendored_typeshed_versions(ty_vendored::file_system());
assert!(versions.len() > 100);
assert!(versions.len() < 1000);
@ -368,8 +366,7 @@ mod tests {
#[test]
fn typeshed_versions_consistent_with_vendored_stubs() {
let db = TestDb::new();
let vendored_typeshed_versions = vendored_typeshed_versions(&db);
let vendored_typeshed_versions = vendored_typeshed_versions(ty_vendored::file_system());
let vendored_typeshed_dir =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../ty_vendored/vendor/typeshed");

View file

@ -1,15 +1,14 @@
use std::borrow::Cow;
use std::sync::Arc;
use crate::Db;
use crate::module_resolver::SearchPaths;
use crate::module_resolver::{SearchPathValidationError, SearchPaths};
use crate::python_platform::PythonPlatform;
use crate::site_packages::SysPrefixPathOrigin;
use anyhow::Context;
use ruff_db::diagnostic::Span;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_db::vendored::VendoredFileSystem;
use ruff_python_ast::PythonVersion;
use ruff_text_size::TextRange;
use salsa::Durability;
@ -28,66 +27,44 @@ pub struct Program {
}
impl Program {
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> anyhow::Result<Self> {
pub fn init_or_update(db: &mut dyn Db, settings: ProgramSettings) -> Self {
match Self::try_get(db) {
Some(program) => {
program.update_from_settings(db, settings);
program
}
None => Self::from_settings(db, settings),
}
}
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> Self {
let ProgramSettings {
python_version: python_version_with_source,
python_version,
python_platform,
search_paths,
} = settings;
let search_paths = SearchPaths::from_settings(db, &search_paths)
.with_context(|| "Invalid search path settings")?;
search_paths.try_register_static_roots(db);
let python_version_with_source =
Self::resolve_python_version(python_version_with_source, &search_paths);
tracing::info!(
"Python version: Python {python_version}, platform: {python_platform}",
python_version = python_version_with_source.version
);
Ok(
Program::builder(python_version_with_source, python_platform, search_paths)
.durability(Durability::HIGH)
.new(db),
)
Program::builder(python_version, python_platform, search_paths)
.durability(Durability::HIGH)
.new(db)
}
pub fn python_version(self, db: &dyn Db) -> PythonVersion {
self.python_version_with_source(db).version
}
fn resolve_python_version(
config_value: Option<PythonVersionWithSource>,
search_paths: &SearchPaths,
) -> PythonVersionWithSource {
config_value
.or_else(|| {
search_paths
.try_resolve_installation_python_version()
.map(Cow::into_owned)
})
.unwrap_or_default()
}
pub fn update_from_settings(
self,
db: &mut dyn Db,
settings: ProgramSettings,
) -> anyhow::Result<()> {
pub fn update_from_settings(self, db: &mut dyn Db, settings: ProgramSettings) {
let ProgramSettings {
python_version: python_version_with_source,
python_version,
python_platform,
search_paths,
} = settings;
let search_paths = SearchPaths::from_settings(db, &search_paths)?;
let new_python_version =
Self::resolve_python_version(python_version_with_source, &search_paths);
if self.search_paths(db) != &search_paths {
tracing::debug!("Updating search paths");
search_paths.try_register_static_roots(db);
self.set_search_paths(db).to(search_paths);
}
@ -96,48 +73,13 @@ impl Program {
self.set_python_platform(db).to(python_platform);
}
if &new_python_version != self.python_version_with_source(db) {
if &python_version != self.python_version_with_source(db) {
tracing::debug!(
"Updating python version: Python {version}",
version = new_python_version.version
version = python_version.version
);
self.set_python_version_with_source(db)
.to(new_python_version);
self.set_python_version_with_source(db).to(python_version);
}
Ok(())
}
/// Update the search paths for the program.
pub fn update_search_paths(
self,
db: &mut dyn Db,
search_path_settings: &SearchPathSettings,
) -> anyhow::Result<()> {
let search_paths = SearchPaths::from_settings(db, search_path_settings)?;
let current_python_version = self.python_version_with_source(db);
let python_version_from_environment = search_paths
.try_resolve_installation_python_version()
.map(Cow::into_owned)
.unwrap_or_default();
if current_python_version != &python_version_from_environment
&& current_python_version.source.priority()
<= python_version_from_environment.source.priority()
{
tracing::debug!("Updating Python version from environment");
self.set_python_version_with_source(db)
.to(python_version_from_environment);
}
if self.search_paths(db) != &search_paths {
tracing::debug!("Updating search paths");
self.set_search_paths(db).to(search_paths);
}
Ok(())
}
pub fn custom_stdlib_search_path(self, db: &dyn Db) -> Option<&SystemPath> {
@ -147,9 +89,9 @@ impl Program {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProgramSettings {
pub python_version: Option<PythonVersionWithSource>,
pub python_version: PythonVersionWithSource,
pub python_platform: PythonPlatform,
pub search_paths: SearchPathSettings,
pub search_paths: SearchPaths,
}
#[derive(Clone, Debug, Eq, PartialEq, Default)]
@ -177,35 +119,6 @@ pub enum PythonVersionSource {
Default,
}
impl PythonVersionSource {
fn priority(&self) -> PythonSourcePriority {
match self {
PythonVersionSource::Default => PythonSourcePriority::Default,
PythonVersionSource::PyvenvCfgFile(_) => PythonSourcePriority::PyvenvCfgFile,
PythonVersionSource::ConfigFile(_) => PythonSourcePriority::ConfigFile,
PythonVersionSource::Cli => PythonSourcePriority::Cli,
PythonVersionSource::InstallationDirectoryLayout { .. } => {
PythonSourcePriority::InstallationDirectoryLayout
}
}
}
}
/// The priority in which Python version sources are considered.
/// The lower down the variant appears in this enum, the higher its priority.
///
/// For example, if a Python version is specified in a pyproject.toml file
/// but *also* via a CLI argument, the CLI argument will take precedence.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
enum PythonSourcePriority {
Default,
InstallationDirectoryLayout,
PyvenvCfgFile,
ConfigFile,
Cli,
}
/// Information regarding the file and [`TextRange`] of the configuration
/// from which we inferred the Python version.
#[derive(Debug, PartialEq, Eq, Clone)]
@ -270,11 +183,26 @@ impl SearchPathSettings {
pub fn new(src_roots: Vec<SystemPathBuf>) -> Self {
Self {
src_roots,
..SearchPathSettings::empty()
}
}
pub fn empty() -> Self {
SearchPathSettings {
src_roots: vec![],
extra_paths: vec![],
custom_typeshed: None,
python_path: PythonPath::KnownSitePackages(vec![]),
}
}
pub fn to_search_paths(
&self,
system: &dyn System,
vendored: &VendoredFileSystem,
) -> Result<SearchPaths, SearchPathValidationError> {
SearchPaths::from_settings(self, system, vendored)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -308,74 +236,3 @@ impl PythonPath {
Self::IntoSysPrefix(path.into(), origin)
}
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn test_python_version_source_priority() {
for priority in PythonSourcePriority::iter() {
match priority {
// CLI source takes priority over all other sources.
PythonSourcePriority::Cli => {
for other in PythonSourcePriority::iter() {
assert!(priority >= other, "{other:?}");
}
}
// Config files have lower priority than CLI arguments,
// but higher than pyvenv.cfg files and the fallback default.
PythonSourcePriority::ConfigFile => {
for other in PythonSourcePriority::iter() {
match other {
PythonSourcePriority::Cli => assert!(other > priority, "{other:?}"),
PythonSourcePriority::ConfigFile => assert_eq!(priority, other),
PythonSourcePriority::PyvenvCfgFile
| PythonSourcePriority::Default
| PythonSourcePriority::InstallationDirectoryLayout => {
assert!(priority > other, "{other:?}");
}
}
}
}
// Pyvenv.cfg files have lower priority than CLI flags and config files,
// but higher than the default fallback.
PythonSourcePriority::PyvenvCfgFile => {
for other in PythonSourcePriority::iter() {
match other {
PythonSourcePriority::Cli | PythonSourcePriority::ConfigFile => {
assert!(other > priority, "{other:?}");
}
PythonSourcePriority::PyvenvCfgFile => assert_eq!(priority, other),
PythonSourcePriority::Default
| PythonSourcePriority::InstallationDirectoryLayout => {
assert!(priority > other, "{other:?}");
}
}
}
}
PythonSourcePriority::InstallationDirectoryLayout => {
for other in PythonSourcePriority::iter() {
match other {
PythonSourcePriority::Cli
| PythonSourcePriority::ConfigFile
| PythonSourcePriority::PyvenvCfgFile => {
assert!(other > priority, "{other:?}");
}
PythonSourcePriority::InstallationDirectoryLayout => {
assert_eq!(priority, other);
}
PythonSourcePriority::Default => assert!(priority > other, "{other:?}"),
}
}
}
PythonSourcePriority::Default => {
for other in PythonSourcePriority::iter() {
assert!(priority <= other, "{other:?}");
}
}
}
}
}
}

View file

@ -532,7 +532,7 @@ impl SystemEnvironment {
/// Enumeration of ways in which `site-packages` discovery can fail.
#[derive(Debug)]
pub(crate) enum SitePackagesDiscoveryError {
pub enum SitePackagesDiscoveryError {
/// `site-packages` discovery failed because the provided path couldn't be canonicalized.
CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, io::Error),
@ -698,7 +698,7 @@ fn display_error(
/// The various ways in which parsing a `pyvenv.cfg` file could fail
#[derive(Debug)]
pub(crate) enum PyvenvCfgParseErrorKind {
pub enum PyvenvCfgParseErrorKind {
MalformedKeyValuePair { line_number: NonZeroUsize },
NoHomeKey,
InvalidHomeValue(io::Error),
@ -853,7 +853,7 @@ fn site_packages_directory_from_sys_prefix(
///
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) struct SysPrefixPath {
pub struct SysPrefixPath {
inner: SystemPathBuf,
origin: SysPrefixPathOrigin,
}