Skip root when assessing prefix viability (#9823)

## Summary

In CPython, it appears that `/` is not considered as a valid path in
`search_up`:

```c
static PyObject *
getpath_dirname(PyObject *Py_UNUSED(self), PyObject *args)
{
    PyObject *path;
    if (!PyArg_ParseTuple(args, "U", &path)) {
        return NULL;
    }
    Py_ssize_t end = PyUnicode_GET_LENGTH(path);
    Py_ssize_t pos = PyUnicode_FindChar(path, SEP, 0, end, -1);
    if (pos < 0) {
        return PyUnicode_FromStringAndSize(NULL, 0);
    }
    return PyUnicode_Substring(path, 0, pos);
}
```

```python
def search_up(prefix, *landmarks, test=isfile):
    while prefix:
        if any(test(joinpath(prefix, f)) for f in landmarks):
            return prefix
        prefix = dirname(prefix)
```

Closes https://github.com/astral-sh/uv/issues/9818.
This commit is contained in:
Charlie Marsh 2024-12-11 14:59:48 -05:00 committed by GitHub
parent a41ef21db9
commit 2ca39f1a2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -655,6 +655,12 @@ fn copy_launcher_windows(
///
/// See: <https://github.com/python/cpython/blob/a03efb533a58fd13fb0cc7f4a5c02c8406a407bd/Modules/getpath.py#L591-L594>
fn find_base_python(executable: &Path, major: u8, minor: u8) -> Result<PathBuf, io::Error> {
/// Returns `true` if `path` is the root directory.
fn is_root(path: &Path) -> bool {
let mut components = path.components();
components.next() == Some(std::path::Component::RootDir) && components.next().is_none()
}
/// Determining whether `dir` is a valid Python prefix by searching for a "landmark".
///
/// See: <https://github.com/python/cpython/blob/a03efb533a58fd13fb0cc7f4a5c02c8406a407bd/Modules/getpath.py#L183>
@ -678,7 +684,7 @@ fn find_base_python(executable: &Path, major: u8, minor: u8) -> Result<PathBuf,
);
// Determine whether this executable will produce a valid `home` for a virtual environment.
for prefix in executable.ancestors() {
for prefix in executable.ancestors().take_while(|path| !is_root(path)) {
if is_prefix(prefix, major, minor) {
return Ok(executable.into_owned());
}