Rewrite Python interpreter discovery (#3266)

Updates our Python interpreter discovery to conform to the rules
described in #2386, please see that issue for a full description of the
behavior. Briefly, we now will search for interpreters that satisfy a
requested version without stopping at the first Python executable.
Additionally, if retrieving information about an interpreter fails we
will continue to search for a working interpreter. We also add the
plumbing necessary to request Python implementations other than CPython,
though we do not add support for other implementations at this time.

A major internal goal of this work is to prepare for user-facing managed
toolchains i.e. fetching a requested version during `uv run`. These APIs
are not introduced, but there is some managed toolchain handling as
required for our test suite.

Some noteworthy implementation changes:

- The `uv_interpreter::find_python` module has been removed in favor of
a `uv_interpreter::discovery` module.
- There are new types to help structure interpreter requests and track
sources
- Executable discovery is implemented as a big lazy iterator and is a
central authority for source precedence
- `uv_interpreter::Error` variants were split into scoped types in each
module
- There's much more unit test coverage, but not for Windows yet

Remaining work:

- [x] Write new test cases
- [x] Determine correct behavior around executables in the current
directory
- _Future_: Combine `PythonVersion` and `VersionRequest`
- _Future_: Consider splitting `ManagedToolchain` into local and remote
variants
- _Future_: Add Windows unit test coverage
- _Future_: Explore behavior around implementation precedence (i.e.
CPython over PyPy)

Refactors split into:

- #3329 
- #3330 
- #3331
- #3332

Closes #2386
This commit is contained in:
Zanie Blue 2024-05-21 15:37:23 -04:00 committed by GitHub
parent c14a7dbef3
commit d540d0f28b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 3100 additions and 1165 deletions

View file

@ -25,11 +25,13 @@ pub(crate) struct FetchPythonArgs {
pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
let start = Instant::now();
let bootstrap_dir = TOOLCHAIN_DIRECTORY
.as_ref()
.expect("The toolchain directory must exist for bootstrapping");
let bootstrap_dir = TOOLCHAIN_DIRECTORY.clone().unwrap_or_else(|| {
std::env::current_dir()
.expect("Use `UV_BOOTSTRAP_DIR` if the current directory is not usable.")
.join("bin")
});
fs_err::create_dir_all(bootstrap_dir)?;
fs_err::create_dir_all(&bootstrap_dir)?;
let versions = if args.versions.is_empty() {
info!("Reading versions from file...");
@ -59,7 +61,7 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
let mut tasks = futures::stream::iter(downloads.iter())
.map(|download| {
async {
let result = download.fetch(&client, bootstrap_dir).await;
let result = download.fetch(&client, &bootstrap_dir).await;
(download.python_version(), result)
}
.instrument(info_span!("download", key = %download))
@ -130,6 +132,10 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
};
info!("Installed {} versions", requests.len());
info!(
r#"To enable discovery: export UV_BOOTSTRAP_DIR="{}""#,
bootstrap_dir.display()
);
Ok(())
}