Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 55 additions & 48 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,76 +339,83 @@ fn python_executables_from_installed<'a>(
preference: PythonPreference,
preview: Preview,
) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
let from_managed_installations = iter::once_with(move || {
ManagedPythonInstallations::from_settings(None)
.map_err(Error::from)
.and_then(|installed_installations| {
debug!(
let extra_managed_install_dirs = env::var_os(EnvVars::UV_PYTHON_EXTRA_INSTALL_DIRS)
.into_iter()
.flat_map(|value| env::split_paths(&value).collect::<Vec<_>>())
.filter(|path| !path.as_os_str().is_empty());

let from_managed_installations = iter::once(None)
.chain(extra_managed_install_dirs.into_iter().map(Some))
.map(move |install_dir| {
ManagedPythonInstallations::from_settings(install_dir)
.map_err(Error::from)
.and_then(|installed_installations| {
debug!(
"Searching for managed installations at `{}`",
installed_installations.root().user_display()
);
let installations = installed_installations.find_matching_current_platform()?;
let installations = installed_installations.find_matching_current_platform()?;

let build_versions = python_build_versions_from_env()?;
let build_versions = python_build_versions_from_env()?;

// Check that the Python version and platform satisfy the request to avoid
// unnecessary interpreter queries later
Ok(installations
.into_iter()
.filter(move |installation| {
if !version.matches_version(&installation.version()) {
debug!("Skipping managed installation `{installation}`: does not satisfy `{version}`");
return false;
}
if !platform.matches(installation.platform()) {
debug!("Skipping managed installation `{installation}`: does not satisfy requested platform `{platform}`");
return false;
}
// Check that the Python version and platform satisfy the request to avoid
// unnecessary interpreter queries later
Ok(installations
.into_iter()
.filter(move |installation| {
if !version.matches_version(&installation.version()) {
debug!("Skipping managed installation `{installation}`: does not satisfy `{version}`");
return false;
}
if !platform.matches(installation.platform()) {
debug!("Skipping managed installation `{installation}`: does not satisfy requested platform `{platform}`");
return false;
}

if let Some(requested_build) = build_versions.get(&installation.implementation()) {
let Some(installation_build) = installation.build() else {
debug!(
if let Some(requested_build) = build_versions.get(&installation.implementation()) {
let Some(installation_build) = installation.build() else {
debug!(
"Skipping managed installation `{installation}`: a build version was requested but is not recorded for this installation"
);
return false;
};
if installation_build != requested_build {
debug!(
return false;
};
if installation_build != requested_build {
debug!(
"Skipping managed installation `{installation}`: requested build version `{requested_build}` does not match installation build version `{installation_build}`"
);
return false;
return false;
}
}
}

true
})
.inspect(|installation| debug!("Found managed installation `{installation}`"))
.map(move |installation| {
// If it's not a patch version request, then attempt to read the stable
// minor version link.
let executable = version
true
})
.inspect(|installation| debug!("Found managed installation `{installation}`"))
.map(move |installation| {
// If it's not a patch version request, then attempt to read the stable
// minor version link.
let executable = version
.patch()
.is_none()
.then(|| {
PythonMinorVersionLink::from_installation(
&installation,
preview,
)
.filter(PythonMinorVersionLink::exists)
.map(
|minor_version_link| {
minor_version_link.symlink_executable.clone()
},
)
.filter(PythonMinorVersionLink::exists)
.map(
|minor_version_link| {
minor_version_link.symlink_executable.clone()
},
)
})
.flatten()
.unwrap_or_else(|| installation.executable(false));
(PythonSource::Managed, executable)
})
)
})
})
.flatten_ok();
(PythonSource::Managed, executable)
})
)
})
})
.flatten_ok();

let from_search_path = iter::once_with(move || {
python_executables_from_search_path(version, implementation)
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-python/src/managed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ impl ManagedPythonInstallations {
) -> Result<impl DoubleEndedIterator<Item = ManagedPythonInstallation> + use<>, Error> {
let platform = Platform::from_env()?;

let iter = Self::from_settings(None)?
let iter = self
.find_all()?
.filter(move |installation| {
if !platform.supports(installation.platform()) {
Expand Down
7 changes: 7 additions & 0 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,13 @@ impl EnvVars {
#[attr_added_in("0.2.22")]
pub const UV_PYTHON_INSTALL_DIR: &'static str = "UV_PYTHON_INSTALL_DIR";

/// Specifies additional directories to search for managed Python installations.
///
/// Directories should be separated by the platform-specific path separator, i.e.,
/// `:` on Unix and `;` on Windows.
#[attr_added_in("next release")]
pub const UV_PYTHON_EXTRA_INSTALL_DIRS: &'static str = "UV_PYTHON_EXTRA_INSTALL_DIRS";

/// Whether to install the Python executable into the `UV_PYTHON_BIN_DIR` directory.
#[attr_added_in("0.8.0")]
pub const UV_PYTHON_INSTALL_BIN: &'static str = "UV_PYTHON_INSTALL_BIN";
Expand Down
Loading