Skip to content

Commit bb73359

Browse files
committed
ty: Discover site-packages from the environment that ty is installed in
1 parent 7569b09 commit bb73359

File tree

3 files changed

+95
-14
lines changed

3 files changed

+95
-14
lines changed

crates/ty/tests/cli/python_environment.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ fn many_search_paths() -> anyhow::Result<()> {
510510
Found 1 diagnostic
511511
512512
----- stderr -----
513+
INFO Using ty's environment for site-packages
513514
INFO Python version: Python 3.14, platform: linux
514515
INFO Indexed 7 file(s) in 0.000s
515516
");

crates/ty_project/src/metadata/options.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,24 @@ impl Options {
164164
.context("Failed to discover local Python environment")?
165165
};
166166

167-
let site_packages_paths = if let Some(python_environment) = python_environment.as_ref() {
167+
let self_site_packages = self_environment_search_paths(
168168
python_environment
169-
.site_packages_paths(system)
170-
.context("Failed to discover the site-packages directory")?
169+
.as_ref()
170+
.map(ty_python_semantic::PythonEnvironment::origin)
171+
.cloned(),
172+
system,
173+
)
174+
.unwrap_or_default();
175+
176+
let site_packages_paths = if let Some(python_environment) = python_environment.as_ref() {
177+
self_site_packages.concatenate(
178+
python_environment
179+
.site_packages_paths(system)
180+
.context("Failed to discover the site-packages directory")?,
181+
)
171182
} else {
172183
tracing::debug!("No virtual environment found");
173-
174-
SitePackagesPaths::default()
184+
self_site_packages
175185
};
176186

177187
let real_stdlib_path = python_environment.as_ref().and_then(|python_environment| {
@@ -461,6 +471,40 @@ impl Options {
461471
}
462472
}
463473

474+
/// Return the site-packages from the environment ty is installed in, as derived from ty's
475+
/// executable.
476+
///
477+
/// If there's an existing environment with an origin that does not allow including site-packages
478+
/// from ty's environment, discovery of ty's environment is skipped and [`None`] is returned.
479+
///
480+
/// Since ty may be executed from an arbitrary non-Python location, errors during discovery of ty's
481+
/// environment are not raised, instead [`None`] is returned.
482+
fn self_environment_search_paths(
483+
existing_origin: Option<SysPrefixPathOrigin>,
484+
system: &dyn System,
485+
) -> Option<SitePackagesPaths> {
486+
if existing_origin.is_some_and(|origin| !origin.allows_extension_with_self_environment()) {
487+
return None;
488+
}
489+
490+
let Ok(exe_path) = std::env::current_exe() else {
491+
return None;
492+
};
493+
let ty_path = SystemPath::from_std_path(exe_path.as_path())?;
494+
495+
let environment = PythonEnvironment::new(ty_path, SysPrefixPathOrigin::SelfEnvironment, system)
496+
.inspect_err(|err| tracing::debug!("Failed to discover ty's environment: {err}"))
497+
.ok()?;
498+
499+
tracing::info!("Using ty's environment for site-packages");
500+
environment
501+
.site_packages_paths(system)
502+
.inspect_err(|err| {
503+
tracing::debug!("Failed to discover site-packages in ty's environment: {err}");
504+
})
505+
.ok()
506+
}
507+
464508
#[derive(
465509
Debug,
466510
Default,

crates/ty_python_semantic/src/site_packages.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ impl SitePackagesPaths {
6262
self.0.extend(other.0);
6363
}
6464

65+
/// Concatenate two instances of [`SitePackagesPaths`].
66+
#[must_use]
67+
pub fn concatenate(mut self, other: Self) -> Self {
68+
for path in other {
69+
self.0.insert(path);
70+
}
71+
self
72+
}
73+
6574
/// Tries to detect the version from the layout of the `site-packages` directory.
6675
pub fn python_version_from_layout(&self) -> Option<PythonVersionWithSource> {
6776
if cfg!(windows) {
@@ -252,6 +261,13 @@ impl PythonEnvironment {
252261
Self::System(env) => env.real_stdlib_directory(system),
253262
}
254263
}
264+
265+
pub fn origin(&self) -> &SysPrefixPathOrigin {
266+
match self {
267+
Self::Virtual(env) => &env.root_path.origin,
268+
Self::System(env) => &env.root_path.origin,
269+
}
270+
}
255271
}
256272

257273
/// Enumeration of the subdirectories of `sys.prefix` that could contain a
@@ -1393,15 +1409,15 @@ impl SysPrefixPath {
13931409
) -> SitePackagesDiscoveryResult<Self> {
13941410
let sys_prefix = if !origin.must_point_directly_to_sys_prefix()
13951411
&& system.is_file(unvalidated_path)
1396-
&& unvalidated_path
1397-
.file_name()
1398-
.is_some_and(|name| name.starts_with("python"))
1399-
{
1400-
// It looks like they passed us a path to a Python executable, e.g. `.venv/bin/python3`.
1401-
// Try to figure out the `sys.prefix` value from the Python executable.
1412+
&& unvalidated_path.file_name().is_some_and(|name| {
1413+
name.starts_with("python")
1414+
|| name.eq_ignore_ascii_case(&format!("ty{}", std::env::consts::EXE_SUFFIX))
1415+
}) {
1416+
// It looks like they passed us a path to an executable, e.g. `.venv/bin/python3`. Try
1417+
// to figure out the `sys.prefix` value from the Python executable.
14021418
let sys_prefix = if cfg!(windows) {
1403-
// On Windows, the relative path to the Python executable from `sys.prefix`
1404-
// is different depending on whether it's a virtual environment or a system installation.
1419+
// On Windows, the relative path to the executable from `sys.prefix` is different
1420+
// depending on whether it's a virtual environment or a system installation.
14051421
// System installations have their executable at `<sys.prefix>/python.exe`,
14061422
// whereas virtual environments have their executable at `<sys.prefix>/Scripts/python.exe`.
14071423
unvalidated_path.parent().and_then(|parent| {
@@ -1586,6 +1602,8 @@ pub enum SysPrefixPathOrigin {
15861602
/// A `.venv` directory was found in the current working directory,
15871603
/// and the `sys.prefix` path is the path to that virtual environment.
15881604
LocalVenv,
1605+
/// The `sys.prefix` path came from the environment ty is installed in.
1606+
SelfEnvironment,
15891607
}
15901608

15911609
impl SysPrefixPathOrigin {
@@ -1598,6 +1616,7 @@ impl SysPrefixPathOrigin {
15981616
| Self::PythonCliFlag
15991617
| Self::Editor
16001618
| Self::DerivedFromPyvenvCfg
1619+
| Self::SelfEnvironment
16011620
| Self::CondaPrefixVar => false,
16021621
}
16031622
}
@@ -1608,13 +1627,29 @@ impl SysPrefixPathOrigin {
16081627
/// the `sys.prefix` directory, e.g. the `--python` CLI flag.
16091628
pub(crate) const fn must_point_directly_to_sys_prefix(&self) -> bool {
16101629
match self {
1611-
Self::PythonCliFlag | Self::ConfigFileSetting(..) | Self::Editor => false,
1630+
Self::PythonCliFlag
1631+
| Self::ConfigFileSetting(..)
1632+
| Self::Editor
1633+
| Self::SelfEnvironment => false,
16121634
Self::VirtualEnvVar
16131635
| Self::CondaPrefixVar
16141636
| Self::DerivedFromPyvenvCfg
16151637
| Self::LocalVenv => true,
16161638
}
16171639
}
1640+
1641+
pub const fn allows_extension_with_self_environment(&self) -> bool {
1642+
match self {
1643+
Self::SelfEnvironment
1644+
| Self::CondaPrefixVar
1645+
| Self::VirtualEnvVar
1646+
| Self::Editor
1647+
| Self::DerivedFromPyvenvCfg
1648+
| Self::ConfigFileSetting(..)
1649+
| Self::PythonCliFlag => false,
1650+
Self::LocalVenv => true,
1651+
}
1652+
}
16181653
}
16191654

16201655
impl std::fmt::Display for SysPrefixPathOrigin {
@@ -1627,6 +1662,7 @@ impl std::fmt::Display for SysPrefixPathOrigin {
16271662
Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"),
16281663
Self::LocalVenv => f.write_str("local virtual environment"),
16291664
Self::Editor => f.write_str("selected interpreter in your editor"),
1665+
Self::SelfEnvironment => f.write_str("ty environment"),
16301666
}
16311667
}
16321668
}

0 commit comments

Comments
 (0)