mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Look for site-packages
directories in <sys.prefix>/lib64/
as well as <sys.prefix>/lib/
on non-Windows systems (#19978)
This commit is contained in:
parent
e5c091b850
commit
600245478c
2 changed files with 216 additions and 97 deletions
|
@ -345,6 +345,51 @@ import bar",
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On Unix systems, a virtual environment can come with multiple `site-packages` directories:
|
||||||
|
/// one at `<sys.prefix>/lib/pythonX.Y/site-packages` and one at
|
||||||
|
/// `<sys.prefix>/lib64/pythonX.Y/site-packages`. According to [the stdlib docs], the `lib64`
|
||||||
|
/// is not *meant* to have any Python files in it (only C extensions and similar). Empirically,
|
||||||
|
/// however, it sometimes does indeed have Python files in it: popular tools such as poetry
|
||||||
|
/// appear to sometimes install Python packages into the `lib64` site-packages directory even
|
||||||
|
/// though they probably shouldn't. We therefore check for both a `lib64` and a `lib` directory,
|
||||||
|
/// and add them both as search paths if they both exist.
|
||||||
|
///
|
||||||
|
/// See:
|
||||||
|
/// - <https://github.com/astral-sh/ty/issues/1043>
|
||||||
|
/// - <https://github.com/astral-sh/ty/issues/257>.
|
||||||
|
///
|
||||||
|
/// [the stdlib docs]: https://docs.python.org/3/library/sys.html#sys.platlibdir
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn lib64_site_packages_directory_on_unix() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
(".venv/lib/python3.13/site-packages/foo.py", ""),
|
||||||
|
(".venv/lib64/python3.13/site-packages/bar.py", ""),
|
||||||
|
("test.py", "import foo, bar, baz"),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--python").arg(".venv"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error[unresolved-import]: Cannot resolve imported module `baz`
|
||||||
|
--> test.py:1:18
|
||||||
|
|
|
||||||
|
1 | import foo, bar, baz
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||||
|
info: rule `unresolved-import` is enabled by default
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> {
|
fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> {
|
||||||
let case = CliTest::with_files([
|
let case = CliTest::with_files([
|
||||||
|
@ -762,7 +807,7 @@ fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> {
|
||||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
ty failed
|
ty failed
|
||||||
Cause: Failed to discover the site-packages directory
|
Cause: Failed to discover the site-packages directory
|
||||||
Cause: Failed to iterate over the contents of the `lib` directory of the Python installation
|
Cause: Failed to iterate over the contents of the `lib`/`lib64` directories of the Python installation
|
||||||
|
|
||||||
--> Invalid setting in configuration file `<temp_dir>/pyproject.toml`
|
--> Invalid setting in configuration file `<temp_dir>/pyproject.toml`
|
||||||
|
|
|
|
||||||
|
@ -771,8 +816,6 @@ fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> {
|
||||||
3 | python = "directory-but-no-site-packages"
|
3 | python = "directory-but-no-site-packages"
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
Cause: No such file or directory (os error 2)
|
|
||||||
"#);
|
"#);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -23,6 +23,7 @@ use ruff_python_ast::PythonVersion;
|
||||||
use ruff_python_trivia::Cursor;
|
use ruff_python_trivia::Cursor;
|
||||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
use ty_static::EnvVars;
|
use ty_static::EnvVars;
|
||||||
|
|
||||||
type SitePackagesDiscoveryResult<T> = Result<T, SitePackagesDiscoveryError>;
|
type SitePackagesDiscoveryResult<T> = Result<T, SitePackagesDiscoveryError>;
|
||||||
|
@ -49,8 +50,8 @@ type StdlibDiscoveryResult<T> = Result<T, StdlibDiscoveryError>;
|
||||||
pub struct SitePackagesPaths(IndexSet<SystemPathBuf>);
|
pub struct SitePackagesPaths(IndexSet<SystemPathBuf>);
|
||||||
|
|
||||||
impl SitePackagesPaths {
|
impl SitePackagesPaths {
|
||||||
fn single(path: SystemPathBuf) -> Self {
|
fn is_empty(&self) -> bool {
|
||||||
Self(IndexSet::from([path]))
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, path: SystemPathBuf) {
|
fn insert(&mut self, path: SystemPathBuf) {
|
||||||
|
@ -88,7 +89,7 @@ impl SitePackagesPaths {
|
||||||
|
|
||||||
let parent_component = site_packages_ancestor_components.next()?;
|
let parent_component = site_packages_ancestor_components.next()?;
|
||||||
|
|
||||||
if site_packages_ancestor_components.next()? != "lib" {
|
if site_packages_ancestor_components.next()? != UnixLibDir::Lib {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +111,9 @@ impl SitePackagesPaths {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<SystemPathBuf> for SitePackagesPaths {
|
impl<const N: usize> From<[SystemPathBuf; N]> for SitePackagesPaths {
|
||||||
fn from_iter<T: IntoIterator<Item = SystemPathBuf>>(iter: T) -> Self {
|
fn from(paths: [SystemPathBuf; N]) -> Self {
|
||||||
Self(IndexSet::from_iter(iter))
|
Self(IndexSet::from(paths))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +241,52 @@ impl PythonEnvironment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumeration of the subdirectories of `sys.prefix` that could contain a
|
||||||
|
/// `site-packages` directory if the host system is Unix-like.
|
||||||
|
///
|
||||||
|
/// For example, if `sys.prefix` is `.venv` and the Python version is 3.10,
|
||||||
|
/// the `site-packages` directory could be located at `.venv/lib/python3.10/site-packages`,
|
||||||
|
/// or at `.venv/lib64/python3.10/site-packages`, or there could indeed be `site-packages`
|
||||||
|
/// directories at both of these locations.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, strum_macros::EnumIter)]
|
||||||
|
enum UnixLibDir {
|
||||||
|
Lib,
|
||||||
|
Lib64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnixLibDir {
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Lib => "lib",
|
||||||
|
Self::Lib64 => "lib64",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UnixLibDir {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<SystemPath> for UnixLibDir {
|
||||||
|
fn as_ref(&self) -> &SystemPath {
|
||||||
|
SystemPath::new(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for UnixLibDir {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
self.as_str() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<UnixLibDir> for &str {
|
||||||
|
fn eq(&self, other: &UnixLibDir) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The Python runtime that produced the venv.
|
/// The Python runtime that produced the venv.
|
||||||
///
|
///
|
||||||
/// We only need to distinguish cases that change the on-disk layout.
|
/// We only need to distinguish cases that change the on-disk layout.
|
||||||
|
@ -258,12 +305,16 @@ pub(crate) enum PythonImplementation {
|
||||||
impl PythonImplementation {
|
impl PythonImplementation {
|
||||||
/// Return the relative path from `sys.prefix` to the `site-packages` directory
|
/// Return the relative path from `sys.prefix` to the `site-packages` directory
|
||||||
/// if this is a known implementation. Return `None` if this is an unknown implementation.
|
/// if this is a known implementation. Return `None` if this is an unknown implementation.
|
||||||
fn relative_site_packages_path(self, version: Option<PythonVersion>) -> Option<String> {
|
fn relative_site_packages_path(
|
||||||
|
self,
|
||||||
|
lib_dir: UnixLibDir,
|
||||||
|
version: Option<PythonVersion>,
|
||||||
|
) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
Self::CPython | Self::GraalPy => {
|
Self::CPython | Self::GraalPy => {
|
||||||
version.map(|version| format!("lib/python{version}/site-packages"))
|
version.map(|version| format!("{lib_dir}/python{version}/site-packages"))
|
||||||
}
|
}
|
||||||
Self::PyPy => version.map(|version| format!("lib/pypy{version}/site-packages")),
|
Self::PyPy => version.map(|version| format!("{lib_dir}/pypy{version}/site-packages")),
|
||||||
Self::Unknown => None,
|
Self::Unknown => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,7 +464,7 @@ impl VirtualEnvironment {
|
||||||
|
|
||||||
/// Return a list of `site-packages` directories that are available from this virtual environment
|
/// Return a list of `site-packages` directories that are available from this virtual environment
|
||||||
///
|
///
|
||||||
/// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details.
|
/// See the documentation for [`site_packages_directories_from_sys_prefix`] for more details.
|
||||||
pub(crate) fn site_packages_directories(
|
pub(crate) fn site_packages_directories(
|
||||||
&self,
|
&self,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
|
@ -429,9 +480,8 @@ impl VirtualEnvironment {
|
||||||
|
|
||||||
let version = version.as_ref().map(|v| v.version);
|
let version = version.as_ref().map(|v| v.version);
|
||||||
|
|
||||||
let mut site_packages_directories = SitePackagesPaths::single(
|
let mut site_packages_directories =
|
||||||
site_packages_directory_from_sys_prefix(root_path, version, *implementation, system)?,
|
site_packages_directories_from_sys_prefix(root_path, version, *implementation, system)?;
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(parent_env_site_packages) = parent_environment.as_deref() {
|
if let Some(parent_env_site_packages) = parent_environment.as_deref() {
|
||||||
match parent_env_site_packages.site_packages_paths(system) {
|
match parent_env_site_packages.site_packages_paths(system) {
|
||||||
|
@ -456,14 +506,14 @@ impl VirtualEnvironment {
|
||||||
// or if we fail to resolve the `site-packages` from the `sys.prefix` path,
|
// or if we fail to resolve the `site-packages` from the `sys.prefix` path,
|
||||||
// we should probably print a warning but *not* abort type checking
|
// we should probably print a warning but *not* abort type checking
|
||||||
if let Some(sys_prefix_path) = system_sys_prefix {
|
if let Some(sys_prefix_path) = system_sys_prefix {
|
||||||
match site_packages_directory_from_sys_prefix(
|
match site_packages_directories_from_sys_prefix(
|
||||||
&sys_prefix_path,
|
&sys_prefix_path,
|
||||||
version,
|
version,
|
||||||
*implementation,
|
*implementation,
|
||||||
system,
|
system,
|
||||||
) {
|
) {
|
||||||
Ok(site_packages_directory) => {
|
Ok(system_directories) => {
|
||||||
site_packages_directories.insert(site_packages_directory);
|
site_packages_directories.extend(system_directories);
|
||||||
}
|
}
|
||||||
Err(error) => tracing::warn!(
|
Err(error) => tracing::warn!(
|
||||||
"{error}. System site-packages will not be used for module resolution."
|
"{error}. System site-packages will not be used for module resolution."
|
||||||
|
@ -665,7 +715,7 @@ impl SystemEnvironment {
|
||||||
/// Create a new system environment from the given path.
|
/// Create a new system environment from the given path.
|
||||||
///
|
///
|
||||||
/// At this time, there is no eager validation and this is infallible. Instead, validation
|
/// At this time, there is no eager validation and this is infallible. Instead, validation
|
||||||
/// will occur in [`site_packages_directory_from_sys_prefix`] — which will fail if there is not
|
/// will occur in [`site_packages_directories_from_sys_prefix`] — which will fail if there is not
|
||||||
/// a Python environment at the given path.
|
/// a Python environment at the given path.
|
||||||
pub(crate) fn new(path: SysPrefixPath) -> Self {
|
pub(crate) fn new(path: SysPrefixPath) -> Self {
|
||||||
Self { root_path: path }
|
Self { root_path: path }
|
||||||
|
@ -673,20 +723,19 @@ impl SystemEnvironment {
|
||||||
|
|
||||||
/// Return a list of `site-packages` directories that are available from this environment.
|
/// Return a list of `site-packages` directories that are available from this environment.
|
||||||
///
|
///
|
||||||
/// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details.
|
/// See the documentation for [`site_packages_directories_from_sys_prefix`] for more details.
|
||||||
pub(crate) fn site_packages_directories(
|
pub(crate) fn site_packages_directories(
|
||||||
&self,
|
&self,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
) -> SitePackagesDiscoveryResult<SitePackagesPaths> {
|
) -> SitePackagesDiscoveryResult<SitePackagesPaths> {
|
||||||
let SystemEnvironment { root_path } = self;
|
let SystemEnvironment { root_path } = self;
|
||||||
|
|
||||||
let site_packages_directories =
|
let site_packages_directories = site_packages_directories_from_sys_prefix(
|
||||||
SitePackagesPaths::single(site_packages_directory_from_sys_prefix(
|
|
||||||
root_path,
|
root_path,
|
||||||
None,
|
None,
|
||||||
PythonImplementation::Unknown,
|
PythonImplementation::Unknown,
|
||||||
system,
|
system,
|
||||||
)?);
|
)?;
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Resolved site-packages directories for this environment are: {site_packages_directories:?}"
|
"Resolved site-packages directories for this environment are: {site_packages_directories:?}"
|
||||||
|
@ -696,7 +745,7 @@ impl SystemEnvironment {
|
||||||
|
|
||||||
/// Return a list of `site-packages` directories that are available from this environment.
|
/// Return a list of `site-packages` directories that are available from this environment.
|
||||||
///
|
///
|
||||||
/// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details.
|
/// See the documentation for [`site_packages_directories_from_sys_prefix`] for more details.
|
||||||
pub(crate) fn real_stdlib_directory(
|
pub(crate) fn real_stdlib_directory(
|
||||||
&self,
|
&self,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
|
@ -740,7 +789,7 @@ pub enum SitePackagesDiscoveryError {
|
||||||
/// would be relative to the `sys.prefix` path, and we tried to fallback to iterating
|
/// would be relative to the `sys.prefix` path, and we tried to fallback to iterating
|
||||||
/// through the `<sys.prefix>/lib` directory looking for a `site-packages` directory,
|
/// through the `<sys.prefix>/lib` directory looking for a `site-packages` directory,
|
||||||
/// but we came across some I/O error while trying to do so.
|
/// but we came across some I/O error while trying to do so.
|
||||||
CouldNotReadLibDirectory(SysPrefixPath, io::Error),
|
CouldNotReadLibDirectory(SysPrefixPath),
|
||||||
|
|
||||||
/// We looked everywhere we could think of for the `site-packages` directory,
|
/// We looked everywhere we could think of for the `site-packages` directory,
|
||||||
/// but none could be found despite our best endeavours.
|
/// but none could be found despite our best endeavours.
|
||||||
|
@ -771,9 +820,9 @@ impl std::error::Error for SitePackagesDiscoveryError {
|
||||||
io_err.as_ref().map(|e| e as &dyn std::error::Error)
|
io_err.as_ref().map(|e| e as &dyn std::error::Error)
|
||||||
}
|
}
|
||||||
Self::NoPyvenvCfgFile(_, io_err) => Some(io_err),
|
Self::NoPyvenvCfgFile(_, io_err) => Some(io_err),
|
||||||
Self::PyvenvCfgParseError(_, _) => None,
|
Self::PyvenvCfgParseError(_, _)
|
||||||
Self::CouldNotReadLibDirectory(_, io_err) => Some(io_err),
|
| Self::CouldNotReadLibDirectory(_)
|
||||||
Self::NoSitePackagesDirFound(_) => None,
|
| Self::NoSitePackagesDirFound(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -811,11 +860,11 @@ impl std::fmt::Display for SitePackagesDiscoveryError {
|
||||||
"Failed to parse the `pyvenv.cfg` file at `{path}` because {kind}"
|
"Failed to parse the `pyvenv.cfg` file at `{path}` because {kind}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::CouldNotReadLibDirectory(SysPrefixPath { inner, origin }, _) => display_error(
|
Self::CouldNotReadLibDirectory(SysPrefixPath { inner, origin }) => display_error(
|
||||||
f,
|
f,
|
||||||
origin,
|
origin,
|
||||||
inner,
|
inner,
|
||||||
"Failed to iterate over the contents of the `lib` directory of the Python installation",
|
"Failed to iterate over the contents of the `lib`/`lib64` directories of the Python installation",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
Self::NoSitePackagesDirFound(SysPrefixPath { inner, origin }) => display_error(
|
Self::NoSitePackagesDirFound(SysPrefixPath { inner, origin }) => display_error(
|
||||||
|
@ -963,19 +1012,19 @@ when trying to resolve the `home` value to a directory on disk: {io_err}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to retrieve the `site-packages` directory
|
/// Attempt to retrieve the `site-packages` directories
|
||||||
/// associated with a given Python installation.
|
/// associated with a given Python installation.
|
||||||
///
|
///
|
||||||
/// The location of the `site-packages` directory can vary according to the
|
/// The location of the `site-packages` directories can vary according to the
|
||||||
/// Python version that this installation represents. The Python version may
|
/// Python version that this installation represents. The Python version may
|
||||||
/// or may not be known at this point, which is why the `python_version`
|
/// or may not be known at this point, which is why the `python_version`
|
||||||
/// parameter is an `Option`.
|
/// parameter is an `Option`.
|
||||||
fn site_packages_directory_from_sys_prefix(
|
fn site_packages_directories_from_sys_prefix(
|
||||||
sys_prefix_path: &SysPrefixPath,
|
sys_prefix_path: &SysPrefixPath,
|
||||||
python_version: Option<PythonVersion>,
|
python_version: Option<PythonVersion>,
|
||||||
implementation: PythonImplementation,
|
implementation: PythonImplementation,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
) -> SitePackagesDiscoveryResult<SystemPathBuf> {
|
) -> SitePackagesDiscoveryResult<SitePackagesPaths> {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Searching for site-packages directory in sys.prefix {}",
|
"Searching for site-packages directory in sys.prefix {}",
|
||||||
sys_prefix_path.inner
|
sys_prefix_path.inner
|
||||||
|
@ -985,10 +1034,10 @@ fn site_packages_directory_from_sys_prefix(
|
||||||
let site_packages = sys_prefix_path.join(r"Lib\site-packages");
|
let site_packages = sys_prefix_path.join(r"Lib\site-packages");
|
||||||
return system
|
return system
|
||||||
.is_directory(&site_packages)
|
.is_directory(&site_packages)
|
||||||
.then_some(site_packages)
|
.then(|| SitePackagesPaths::from([site_packages]))
|
||||||
.ok_or(SitePackagesDiscoveryError::NoSitePackagesDirFound(
|
.ok_or_else(|| {
|
||||||
sys_prefix_path.to_owned(),
|
SitePackagesDiscoveryError::NoSitePackagesDirFound(sys_prefix_path.to_owned())
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the Python standard library's `site.py` module (used for finding `site-packages`
|
// In the Python standard library's `site.py` module (used for finding `site-packages`
|
||||||
|
@ -1000,42 +1049,51 @@ fn site_packages_directory_from_sys_prefix(
|
||||||
// libdirs.append("lib")
|
// libdirs.append("lib")
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// Pyright therefore searches for both a `lib/python3.X/site-packages` directory
|
// We generally only care about the `site-packages` directory insofar as it allows
|
||||||
// and a `lib64/python3.X/site-packages` directory on non-MacOS Unix systems,
|
|
||||||
// since `sys.platlibdir` can sometimes be equal to `"lib64"`.
|
|
||||||
//
|
|
||||||
// However, we only care about the `site-packages` directory insofar as it allows
|
|
||||||
// us to discover Python source code that can be used for inferring type
|
// us to discover Python source code that can be used for inferring type
|
||||||
// information regarding third-party dependencies. That means that we don't need
|
// information regarding third-party dependencies. In theory, therefore, that means
|
||||||
// to care about any possible `lib64/site-packages` directories, since
|
// that we don't need to care about any possible `lib64/site-packages` directories,
|
||||||
// [the `sys`-module documentation] states that `sys.platlibdir` is *only* ever
|
// since [the `sys`-module documentation] states that `sys.platlibdir` is *only* ever
|
||||||
// used for C extensions, never for pure-Python modules.
|
// used for C extensions, never for pure-Python modules. However, in practice,
|
||||||
|
// some installers appear to do [some strange things on Fedora] that mean that `.py`
|
||||||
|
// files *can* end up in `lib64/site-packages` in some edge cases. And we'll probably
|
||||||
|
// need to start looking in `lib64/site-packages` directories in the future anyway, in
|
||||||
|
// order to distinguish between "unresolved import" and "resolved to an opaque C
|
||||||
|
// extension" in diagnostic messages.
|
||||||
//
|
//
|
||||||
// [the non-Windows branch]: https://github.com/python/cpython/blob/a8be8fc6c4682089be45a87bd5ee1f686040116c/Lib/site.py#L401-L410
|
// [the non-Windows branch]: https://github.com/python/cpython/blob/a8be8fc6c4682089be45a87bd5ee1f686040116c/Lib/site.py#L401-L410
|
||||||
// [the `sys`-module documentation]: https://docs.python.org/3/library/sys.html#sys.platlibdir
|
// [the `sys`-module documentation]: https://docs.python.org/3/library/sys.html#sys.platlibdir
|
||||||
|
// [some strange things on Fedora]: https://github.com/astral-sh/ty/issues/1043
|
||||||
|
|
||||||
|
let mut directories = SitePackagesPaths::default();
|
||||||
|
|
||||||
// If we were able to figure out what Python version this installation is,
|
// If we were able to figure out what Python version this installation is,
|
||||||
// we should be able to avoid iterating through all items in the `lib/` directory:
|
// we should be able to avoid iterating through all items in the `lib/` and `lib64/` directories:
|
||||||
if let Some(expected_relative_path) = implementation.relative_site_packages_path(python_version)
|
for lib_dir in UnixLibDir::iter() {
|
||||||
|
if let Some(expected_relative_path) =
|
||||||
|
implementation.relative_site_packages_path(lib_dir, python_version)
|
||||||
{
|
{
|
||||||
let expected_absolute_path = sys_prefix_path.join(expected_relative_path);
|
let expected_absolute_path = sys_prefix_path.join(expected_relative_path);
|
||||||
if system.is_directory(&expected_absolute_path) {
|
if system.is_directory(&expected_absolute_path) {
|
||||||
return Ok(expected_absolute_path);
|
directories.insert(expected_absolute_path);
|
||||||
}
|
} else if matches!(implementation, PythonImplementation::CPython)
|
||||||
|
|
||||||
// CPython free-threaded (3.13+) variant: pythonXYt
|
|
||||||
if matches!(implementation, PythonImplementation::CPython)
|
|
||||||
&& python_version.is_some_and(PythonVersion::free_threaded_build_available)
|
&& python_version.is_some_and(PythonVersion::free_threaded_build_available)
|
||||||
{
|
{
|
||||||
|
// CPython free-threaded (3.13+) variant: pythonX.Yt
|
||||||
let alternative_path = sys_prefix_path.join(format!(
|
let alternative_path = sys_prefix_path.join(format!(
|
||||||
"lib/python{}t/site-packages",
|
"{lib_dir}/python{}t/site-packages",
|
||||||
python_version.unwrap()
|
python_version.unwrap()
|
||||||
));
|
));
|
||||||
if system.is_directory(&alternative_path) {
|
if system.is_directory(&alternative_path) {
|
||||||
return Ok(alternative_path);
|
directories.insert(alternative_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !directories.is_empty() {
|
||||||
|
return Ok(directories);
|
||||||
|
}
|
||||||
|
|
||||||
// Either we couldn't figure out the version before calling this function
|
// Either we couldn't figure out the version before calling this function
|
||||||
// (e.g., from a `pyvenv.cfg` file if this was a venv),
|
// (e.g., from a `pyvenv.cfg` file if this was a venv),
|
||||||
|
@ -1045,12 +1103,17 @@ fn site_packages_directory_from_sys_prefix(
|
||||||
// Note: the `python3.x` part of the `site-packages` path can't be computed from
|
// Note: the `python3.x` part of the `site-packages` path can't be computed from
|
||||||
// the `--python-version` the user has passed, as they might be running Python 3.12 locally
|
// the `--python-version` the user has passed, as they might be running Python 3.12 locally
|
||||||
// even if they've requested that we type check their code "as if" they're running 3.8.
|
// even if they've requested that we type check their code "as if" they're running 3.8.
|
||||||
for entry_result in system
|
let mut found_at_least_one_lib_dir = false;
|
||||||
.read_directory(&sys_prefix_path.join("lib"))
|
|
||||||
.map_err(|io_err| {
|
for lib_dir in UnixLibDir::iter() {
|
||||||
SitePackagesDiscoveryError::CouldNotReadLibDirectory(sys_prefix_path.to_owned(), io_err)
|
let Ok(directory_iterator) = system.read_directory(&sys_prefix_path.join(lib_dir)) else {
|
||||||
})?
|
tracing::debug!("Could not find a `<sys.prefix>/{lib_dir}` directory; continuing");
|
||||||
{
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
found_at_least_one_lib_dir = true;
|
||||||
|
|
||||||
|
for entry_result in directory_iterator {
|
||||||
let Ok(entry) = entry_result else {
|
let Ok(entry) = entry_result else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -1061,9 +1124,9 @@ fn site_packages_directory_from_sys_prefix(
|
||||||
|
|
||||||
let mut path = entry.into_path();
|
let mut path = entry.into_path();
|
||||||
|
|
||||||
let name = path
|
let name = path.file_name().unwrap_or_else(|| panic!(
|
||||||
.file_name()
|
"File name should be non-null because path is guaranteed to be a child of `{lib_dir}`",
|
||||||
.expect("File name to be non-null because path is guaranteed to be a child of `lib`");
|
));
|
||||||
|
|
||||||
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1071,12 +1134,24 @@ fn site_packages_directory_from_sys_prefix(
|
||||||
|
|
||||||
path.push("site-packages");
|
path.push("site-packages");
|
||||||
if system.is_directory(&path) {
|
if system.is_directory(&path) {
|
||||||
return Ok(path);
|
directories.insert(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if directories.is_empty() {
|
||||||
|
if found_at_least_one_lib_dir {
|
||||||
Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(
|
Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(
|
||||||
sys_prefix_path.to_owned(),
|
sys_prefix_path.to_owned(),
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Err(SitePackagesDiscoveryError::CouldNotReadLibDirectory(
|
||||||
|
sys_prefix_path.to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(directories)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to retrieve the real stdlib directory
|
/// Attempt to retrieve the real stdlib directory
|
||||||
|
@ -1133,7 +1208,8 @@ fn real_stdlib_directory_from_sys_prefix(
|
||||||
// the `--python-version` the user has passed, as they might be running Python 3.12 locally
|
// the `--python-version` the user has passed, as they might be running Python 3.12 locally
|
||||||
// even if they've requested that we type check their code "as if" they're running 3.8.
|
// even if they've requested that we type check their code "as if" they're running 3.8.
|
||||||
for entry_result in system
|
for entry_result in system
|
||||||
.read_directory(&sys_prefix_path.join("lib"))
|
// must be `lib`, not `lib64`, for the stdlib
|
||||||
|
.read_directory(&sys_prefix_path.join(UnixLibDir::Lib))
|
||||||
.map_err(|io_err| {
|
.map_err(|io_err| {
|
||||||
StdlibDiscoveryError::CouldNotReadLibDirectory(sys_prefix_path.to_owned(), io_err)
|
StdlibDiscoveryError::CouldNotReadLibDirectory(sys_prefix_path.to_owned(), io_err)
|
||||||
})?
|
})?
|
||||||
|
@ -1148,9 +1224,9 @@ fn real_stdlib_directory_from_sys_prefix(
|
||||||
|
|
||||||
let path = entry.into_path();
|
let path = entry.into_path();
|
||||||
|
|
||||||
let name = path
|
let name = path.file_name().expect(
|
||||||
.file_name()
|
"File name should be non-null because path is guaranteed to be a child of `lib`",
|
||||||
.expect("File name to be non-null because path is guaranteed to be a child of `lib`");
|
);
|
||||||
|
|
||||||
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1314,7 +1390,7 @@ impl SysPrefixPath {
|
||||||
let path = entry.into_path();
|
let path = entry.into_path();
|
||||||
|
|
||||||
let name = path.file_name().expect(
|
let name = path.file_name().expect(
|
||||||
"File name to be non-null because path is guaranteed to be a child of `lib`",
|
"File name should be non-null because path is guaranteed to be a child of `lib`",
|
||||||
);
|
);
|
||||||
|
|
||||||
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue