mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-25 17:38:21 +00:00
## Summary Per #5209, we only show periods in messages when the message itself spans more than a single sentence.
94 lines
3.2 KiB
Rust
94 lines
3.2 KiB
Rust
use std::io;
|
|
use std::path::PathBuf;
|
|
use std::process::{Command, ExitStatus};
|
|
|
|
use once_cell::sync::Lazy;
|
|
use regex::Regex;
|
|
use thiserror::Error;
|
|
use tracing::info_span;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct PyListPath {
|
|
pub(crate) major: u8,
|
|
pub(crate) minor: u8,
|
|
pub(crate) executable_path: PathBuf,
|
|
}
|
|
|
|
/// An error was encountered when using the `py` launcher on Windows.
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
#[error("{message} with {exit_code}\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")]
|
|
StatusCode {
|
|
message: String,
|
|
exit_code: ExitStatus,
|
|
stdout: String,
|
|
stderr: String,
|
|
},
|
|
#[error("Failed to run `py --list-paths` to find Python installations")]
|
|
Io(#[source] io::Error),
|
|
#[error("The `py` launcher could not be found")]
|
|
NotFound,
|
|
}
|
|
|
|
/// ```text
|
|
/// -V:3.12 C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
|
|
/// -V:3.8 C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
|
|
/// ```
|
|
static PY_LIST_PATHS: Lazy<Regex> = Lazy::new(|| {
|
|
// Without the `R` flag, paths have trailing \r
|
|
Regex::new(r"(?mR)^ -(?:V:)?(\d).(\d+)-?(?:arm)?\d*\s*\*?\s*(.*)$").unwrap()
|
|
});
|
|
|
|
/// Use the `py` launcher to find installed Python versions.
|
|
///
|
|
/// Calls `py --list-paths`.
|
|
pub(crate) fn py_list_paths() -> Result<Vec<PyListPath>, Error> {
|
|
// konstin: The command takes 8ms on my machine.
|
|
let output = info_span!("py_list_paths")
|
|
.in_scope(|| Command::new("py").arg("--list-paths").output())
|
|
.map_err(|err| {
|
|
if err.kind() == std::io::ErrorKind::NotFound {
|
|
Error::NotFound
|
|
} else {
|
|
Error::Io(err)
|
|
}
|
|
})?;
|
|
|
|
// `py` sometimes prints "Installed Pythons found by py Launcher for Windows" to stderr which we ignore.
|
|
if !output.status.success() {
|
|
return Err(Error::StatusCode {
|
|
message: format!(
|
|
"Running `py --list-paths` failed with status {}",
|
|
output.status
|
|
),
|
|
exit_code: output.status,
|
|
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
|
|
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
|
|
});
|
|
}
|
|
|
|
// Find the first python of the version we want in the list
|
|
let stdout = String::from_utf8(output.stdout).map_err(|err| Error::StatusCode {
|
|
message: format!("The stdout of `py --list-paths` isn't UTF-8 encoded: {err}"),
|
|
exit_code: output.status,
|
|
stdout: String::from_utf8_lossy(err.as_bytes()).trim().to_string(),
|
|
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
|
|
})?;
|
|
|
|
Ok(PY_LIST_PATHS
|
|
.captures_iter(&stdout)
|
|
.filter_map(|captures| {
|
|
let (_, [major, minor, path]) = captures.extract();
|
|
if let (Some(major), Some(minor)) = (major.parse::<u8>().ok(), minor.parse::<u8>().ok())
|
|
{
|
|
Some(PyListPath {
|
|
major,
|
|
minor,
|
|
executable_path: PathBuf::from(path),
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect())
|
|
}
|