Skip to content

Commit 895e040

Browse files
committed
handle macos facades
1 parent 4790510 commit 895e040

File tree

1 file changed

+76
-1
lines changed

1 file changed

+76
-1
lines changed

crates/ty_python_semantic/src/site_packages.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ System site-packages will not be used for module resolution.",
510510
// of the dir we're looking for.
511511
let version = version.as_ref().map(|v| v.version);
512512
if let Some(system_sys_prefix) =
513-
SysPrefixPath::from_executable_home_path(base_executable_home_path)
513+
SysPrefixPath::from_executable_home_path_real(system, base_executable_home_path)
514514
{
515515
let real_stdlib_directory = real_stdlib_directory_from_sys_prefix(
516516
&system_sys_prefix,
@@ -1243,6 +1243,81 @@ impl SysPrefixPath {
12431243
})
12441244
}
12451245
}
1246+
/// Like `from_executable_home_path` but attempts to resolve through symlink facades
1247+
/// to find a sys prefix that will actually contain the stdlib.
1248+
fn from_executable_home_path_real(system: &dyn System, path: &PythonHomePath) -> Option<Self> {
1249+
let mut home_path = path.0.clone();
1250+
1251+
// Try to find the python executable in the given directory and canonicalize it
1252+
// to resolve any symlink. This is (at least) necessary for homebrew pythons
1253+
// and the macOS system python.
1254+
//
1255+
// In python installations like homebrew, the home path points to a directory like
1256+
// `/opt/homebrew/opt/[email protected]/bin` and indeed if you look for `../lib/python3.13/`
1257+
// you *will* find `site-packages` but you *won't* find the stdlib! (For the macOS
1258+
// system install you won't even find `site-packages` here.)
1259+
//
1260+
// However if you look at `/opt/homebrew/opt/[email protected]/bin/python3.13` (the actual
1261+
// python executable in that dir) you will find that it's a symlink to something like
1262+
// `../Frameworks/Python.framework/Versions/3.13/bin/python3.13`
1263+
//
1264+
// From this Framework binary path if you go to `../../lib/python3.13/` you will then
1265+
// find the python stdlib as expected (and a different instance of site-packages).
1266+
//
1267+
// FIXME: it would be nice to include a "we know the python name" fastpath like in
1268+
// `real_stdlib_directory_from_sys_prefix`.
1269+
if let Ok(dir) = system.read_directory(&home_path) {
1270+
for entry_result in dir {
1271+
let Ok(entry) = entry_result else {
1272+
continue;
1273+
};
1274+
1275+
if entry.file_type().is_directory() {
1276+
continue;
1277+
}
1278+
1279+
let path = entry.into_path();
1280+
1281+
let name = path.file_name().expect(
1282+
"File name to be non-null because path is guaranteed to be a child of `lib`",
1283+
);
1284+
1285+
if !(name.starts_with("python3.") || name.starts_with("pypy3.")) {
1286+
continue;
1287+
}
1288+
1289+
if system.is_directory(&path) {
1290+
continue;
1291+
}
1292+
1293+
let Ok(canonical_path) = system.canonicalize_path(&path) else {
1294+
continue;
1295+
};
1296+
1297+
let Some(parent) = canonical_path.parent() else {
1298+
continue;
1299+
};
1300+
1301+
home_path = parent.to_path_buf();
1302+
break;
1303+
}
1304+
}
1305+
1306+
// No need to check whether `path.parent()` is a directory:
1307+
// the parent of a canonicalised path that is known to exist
1308+
// is guaranteed to be a directory.
1309+
if cfg!(target_os = "windows") {
1310+
Some(Self {
1311+
inner: home_path.to_path_buf(),
1312+
origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg,
1313+
})
1314+
} else {
1315+
home_path.parent().map(|home_path| Self {
1316+
inner: home_path.to_path_buf(),
1317+
origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg,
1318+
})
1319+
}
1320+
}
12461321
}
12471322

12481323
impl Deref for SysPrefixPath {

0 commit comments

Comments
 (0)