use std::fmt::Display;
use std::fmt::Write;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use anstream::eprint;
use anyhow::{Context, bail};
use console::Term;
use itertools::Itertools;
use owo_colors::OwoColorize;
use tokio::process::Command;
use tracing::{debug, warn};
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_cli::ExternalCommand;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_configuration::{Concurrency, Constraints, GitLfsSetting, TargetTriple};
use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::InstalledDist;
use uv_distribution_types::{
IndexCapabilities, IndexUrl, Name, NameRequirementSpecification, Requirement,
RequirementSource, UnresolvedRequirement, UnresolvedRequirementSpecification,
};
use uv_fs::CWD;
use uv_fs::Simplified;
use uv_installer::{InstallationStrategy, SatisfiesResult, SitePackages};
use uv_normalize::PackageName;
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
use uv_pep508::MarkerTree;
use uv_preview::Preview;
use uv_python::PythonVersionFile;
use uv_python::VersionFileDiscoveryOptions;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
PythonPreference, PythonRequest,
};
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_settings::{PythonInstallMirrors, ResolverInstallerOptions, ToolOptions};
use uv_shell::runnable::WindowsRunnable;
use uv_static::EnvVars;
use uv_tool::{InstalledTools, entrypoint_paths};
use uv_warnings::warn_user;
use uv_warnings::warn_user_once;
use uv_workspace::WorkspaceCache;
use crate::child::run_to_completion;
use crate::commands::ExitStatus;
use crate::commands::pip;
use crate::commands::pip::latest::LatestClient;
use crate::commands::pip::loggers::{
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
};
use crate::commands::pip::operations;
use crate::commands::project::{
EnvironmentSpecification, PlatformState, ProjectError, resolve_names,
};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::tool::common::{matching_packages, refine_interpreter};
use crate::commands::tool::{Target, ToolRequest};
use crate::commands::{diagnostics, project::environment::CachedEnvironment};
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;
use crate::settings::ResolverSettings;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum ToolRunCommand {
Uvx,
ToolRun,
}
impl Display for ToolRunCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Uvx => write!(f, "uvx"),
Self::ToolRun => write!(f, "uv tool run"),
}
}
}
fn find_verbose_flag(args: &[std::ffi::OsString]) -> Option<&str> {
args.iter().find_map(|arg| {
let arg_str = arg.to_str()?;
if arg_str == "--verbose" {
Some("--verbose")
} else if arg_str.starts_with("-v") && arg_str.chars().skip(1).all(|c| c == 'v') {
Some(arg_str)
} else {
None
}
})
}
#[expect(clippy::fn_params_excessive_bools)]
pub(crate) async fn run(
command: Option<ExternalCommand>,
from: Option<String>,
with: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
build_constraints: &[RequirementsSource],
show_resolution: bool,
lfs: GitLfsSetting,
python: Option<String>,
python_platform: Option<TargetTriple>,
install_mirrors: PythonInstallMirrors,
options: ResolverInstallerOptions,
settings: ResolverInstallerSettings,
client_builder: BaseClientBuilder<'_>,
invocation_source: ToolRunCommand,
isolated: bool,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
installer_metadata: bool,
concurrency: Concurrency,
cache: Cache,
workspace_cache: WorkspaceCache,
printer: Printer,
env_file: Vec<PathBuf>,
no_env_file: bool,
preview: Preview,
) -> anyhow::Result<ExitStatus> {
fn has_python_script_ext(path: &Path) -> bool {
path.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("py") || ext.eq_ignore_ascii_case("pyw"))
}
if settings.resolver.torch_backend.is_some() {
warn_user_once!(
"The `--torch-backend` option is experimental and may change without warning."
);
}
if !no_env_file {
for env_file_path in env_file.iter().rev().map(PathBuf::as_path) {
match dotenvy::from_path(env_file_path) {
Err(dotenvy::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
bail!(
"No environment file found at: `{}`",
env_file_path.simplified_display()
);
}
Err(dotenvy::Error::Io(err)) => {
bail!(
"Failed to read environment file `{}`: {err}",
env_file_path.simplified_display()
);
}
Err(dotenvy::Error::LineParse(content, position)) => {
warn_user!(
"Failed to parse environment file `{}` at position {position}: {content}",
env_file_path.simplified_display(),
);
}
Err(err) => {
warn_user!(
"Failed to parse environment file `{}`: {err}",
env_file_path.simplified_display(),
);
}
Ok(()) => {
debug!(
"Read environment file at: `{}`",
env_file_path.simplified_display()
);
}
}
}
}
let Some(command) = command else {
show_help(invocation_source, &cache, printer).await?;
return Ok(ExitStatus::Error);
};
let (target, args) = command.split();
let Some(target) = target else {
return Err(anyhow::anyhow!("No tool command provided"));
};
let Some(target) = target.to_str() else {
return Err(anyhow::anyhow!(
"Tool command could not be parsed as UTF-8 string. Use `--from` to specify the package name"
));
};
if let Some(ref from) = from {
if has_python_script_ext(Path::new(from)) {
let package_name = PackageName::from_str(from)?;
return Err(anyhow::anyhow!(
"It looks like you provided a Python script to `--from`, which is not supported\n\n{}{} If you meant to run a command from the `{}` package, use the normalized package name instead to disambiguate, e.g., `{}`",
"hint".bold().cyan(),
":".bold(),
package_name.cyan(),
format!(
"{} --from {} {}",
invocation_source,
package_name.cyan(),
target
)
.green(),
));
}
} else {
let target_path = Path::new(target);
if has_python_script_ext(target_path) {
return if target_path.try_exists()? {
Err(anyhow::anyhow!(
"It looks like you tried to run a Python script at `{}`, which is not supported by `{}`\n\n{}{} Use `{}` instead",
target_path.user_display(),
invocation_source,
"hint".bold().cyan(),
":".bold(),
format!("uv run {}", target_path.user_display()).green(),
))
} else {
let package_name = PackageName::from_str(target)?;
Err(anyhow::anyhow!(
"It looks like you provided a Python script to run, which is not supported supported by `{}`\n\n{}{} We did not find a script at the requested path. If you meant to run a command from the `{}` package, pass the normalized package name to `--from` to disambiguate, e.g., `{}`",
invocation_source,
"hint".bold().cyan(),
":".bold(),
package_name.cyan(),
format!("{invocation_source} --from {package_name} {target}").green(),
))
};
}
}
let (mut target, mut args) = (target, args);
if from.is_none()
&& invocation_source == ToolRunCommand::Uvx
&& target == "run"
&& settings
.resolver
.index_locations
.indexes()
.all(|index| matches!(index.url, IndexUrl::Pypi(..)))
{
let term = Term::stderr();
if term.is_term() {
let rest = args.iter().map(|s| s.to_string_lossy()).join(" ");
let prompt = format!(
"`{}` invokes the `{}` package. Did you mean `{}`?",
format!("uvx run {rest}").green(),
"run".cyan(),
format!("uvx {rest}").green()
);
let confirmation = uv_console::confirm(&prompt, &term, true)?;
if confirmation {
let Some((next_target, next_args)) = args.split_first() else {
return Err(anyhow::anyhow!("No tool command provided"));
};
let Some(next_target) = next_target.to_str() else {
return Err(anyhow::anyhow!(
"Tool command could not be parsed as UTF-8 string. Use `--from` to specify the package name"
));
};
target = next_target;
args = next_args;
}
}
}
let request = ToolRequest::parse(target, from.as_deref())?;
let cache = if request.is_latest() {
cache.with_refresh(Refresh::All(Timestamp::now()))
} else {
cache
};
let result = Box::pin(get_or_create_environment(
&request,
with,
constraints,
overrides,
build_constraints,
show_resolution,
python.as_deref(),
python_platform,
install_mirrors,
options,
&settings,
&client_builder,
isolated,
lfs,
python_preference,
python_downloads,
installer_metadata,
&concurrency,
&cache,
&workspace_cache,
printer,
preview,
))
.await;
let explicit_from = from.is_some();
let (from, environment) = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
if from.is_none() && invocation_source == ToolRunCommand::Uvx && target == "run" {
let rest = args.iter().map(|s| s.to_string_lossy()).join(" ");
return diagnostics::OperationDiagnostic::with_system_certs(
client_builder.system_certs(),
)
.with_hint(format!(
"`{}` invokes the `{}` package. Did you mean `{}`?",
format!("uvx run {rest}").green(),
"run".cyan(),
format!("uvx {rest}").green()
))
.with_context("tool")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
let diagnostic =
diagnostics::OperationDiagnostic::with_system_certs(client_builder.system_certs());
let diagnostic = if let Some(verbose_flag) = find_verbose_flag(args) {
diagnostic.with_hint(format!(
"You provided `{}` to `{}`. Did you mean to provide it to `{}`? e.g., `{}`",
verbose_flag.cyan(),
target.cyan(),
invocation_source.to_string().cyan(),
format!("{invocation_source} {verbose_flag} {target}").green()
))
} else {
diagnostic.with_context("tool")
};
return diagnostic
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(ProjectError::Requirements(err)) => {
let err = miette::Report::msg(format!("{err}"))
.context("Failed to resolve `--with` requirement");
eprint!("{err:?}");
return Ok(ExitStatus::Failure);
}
Err(err) => return Err(err.into()),
};
let executable = from.executable();
let site_packages = SitePackages::from_environment(&environment)?;
let provider_hints = match &from {
ToolRequirement::Python { .. } => None,
ToolRequirement::Package { requirement, .. } => Some(ExecutableProviderHints::new(
executable,
requirement,
&site_packages,
invocation_source,
)),
};
if let Some(ref provider_hints) = provider_hints {
if provider_hints.not_from_any() {
if !explicit_from {
writeln!(printer.stderr(), "{provider_hints}")?;
return Ok(ExitStatus::Failure);
}
} else if provider_hints.not_from_expected() {
warn_user_once!("{provider_hints}");
}
}
let mut process = if cfg!(windows) {
WindowsRunnable::from_script_path(environment.scripts(), executable.as_ref()).into()
} else {
Command::new(executable)
};
process.args(args);
let new_path = std::env::join_paths(
std::iter::once(environment.scripts().to_path_buf()).chain(
std::env::var_os(EnvVars::PATH)
.as_ref()
.iter()
.flat_map(std::env::split_paths),
),
)
.context("Failed to build new PATH variable")?;
process.env(EnvVars::PATH, new_path);
let space = if args.is_empty() { "" } else { " " };
debug!(
"Running `{}{space}{}`",
executable,
args.iter().map(|arg| arg.to_string_lossy()).join(" ")
);
let handle = match process.spawn() {
Ok(handle) => Ok(handle),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
if let Some(ref provider_hints) = provider_hints {
if provider_hints.not_from_any() && explicit_from {
writeln!(printer.stderr(), "{provider_hints}")?;
return Ok(ExitStatus::Failure);
}
}
Err(err)
}
Err(err) => Err(err),
}
.with_context(|| format!("Failed to spawn: `{executable}`"))?;
run_to_completion(handle).await
}
fn get_entrypoints(
from: &PackageName,
site_packages: &SitePackages,
) -> anyhow::Result<Vec<(String, PathBuf)>> {
let installed = site_packages.get_packages(from);
let Some(installed_dist) = installed.first().copied() else {
bail!("Expected at least one requirement")
};
Ok(entrypoint_paths(
site_packages,
installed_dist.name(),
installed_dist.version(),
)?)
}
async fn show_help(
invocation_source: ToolRunCommand,
cache: &Cache,
printer: Printer,
) -> anyhow::Result<()> {
let help = format!(
"See `{}` for more information.",
format!("{invocation_source} --help").bold()
);
writeln!(
printer.stdout(),
"Provide a command to run with `{}`.\n",
format!("{invocation_source} <command>").bold()
)?;
let installed_tools = InstalledTools::from_settings()?;
let _lock = match installed_tools.lock().await {
Ok(lock) => lock,
Err(err)
if err
.as_io_error()
.is_some_and(|err| err.kind() == std::io::ErrorKind::NotFound) =>
{
writeln!(printer.stdout(), "{help}")?;
return Ok(());
}
Err(err) => return Err(err.into()),
};
let tools = installed_tools
.tools()?
.into_iter()
.filter_map(|(name, tool)| {
tool.ok().and_then(|_| {
installed_tools
.get_environment(&name, cache)
.ok()
.flatten()
.and_then(|tool_env| tool_env.version().ok())
.map(|version| (name, version))
})
})
.sorted_by(|(name1, ..), (name2, ..)| name1.cmp(name2))
.collect::<Vec<_>>();
if tools.is_empty() {
writeln!(printer.stdout(), "{help}")?;
return Ok(());
}
writeln!(printer.stdout(), "The following tools are installed:\n")?;
for (name, version) in tools {
writeln!(
printer.stdout(),
"- {} v{version}",
format!("{name}").bold()
)?;
}
writeln!(printer.stdout(), "\n{help}")?;
Ok(())
}
#[derive(Debug)]
struct ExecutableProviderHints<'a> {
executable: &'a str,
from: &'a Requirement,
site_packages: &'a SitePackages,
packages: Vec<InstalledDist>,
invocation_source: ToolRunCommand,
}
impl<'a> ExecutableProviderHints<'a> {
fn new(
executable: &'a str,
from: &'a Requirement,
site_packages: &'a SitePackages,
invocation_source: ToolRunCommand,
) -> Self {
let packages = matching_packages(executable, site_packages);
ExecutableProviderHints {
executable,
from,
site_packages,
packages,
invocation_source,
}
}
fn not_from_expected(&self) -> bool {
!self
.packages
.iter()
.any(|package| package.name() == &self.from.name)
}
fn not_from_any(&self) -> bool {
self.packages.is_empty()
}
}
impl std::fmt::Display for ExecutableProviderHints<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
executable,
from,
site_packages,
packages,
invocation_source,
} = self;
match packages.as_slice() {
[] => {
let entrypoints = match get_entrypoints(&from.name, site_packages) {
Ok(entrypoints) => entrypoints,
Err(err) => {
warn!("Failed to get entrypoints for `{from}`: {err}");
return Ok(());
}
};
if entrypoints.is_empty() {
write!(
f,
"Package `{}` does not provide any executables.",
from.name.red()
)?;
return Ok(());
}
writeln!(
f,
"An executable named `{}` is not provided by package `{}`.",
executable.cyan(),
from.name.cyan(),
)?;
writeln!(f, "The following executables are available:")?;
for (name, _) in &entrypoints {
writeln!(f, "- {}", name.cyan())?;
}
let name = match entrypoints.as_slice() {
[entrypoint] => entrypoint.0.as_str(),
_ => "<EXECUTABLE-NAME>",
};
if *executable == from.name.as_str() {
let suggested_command =
format!("{} --from {} {name}", invocation_source, from.name);
writeln!(f, "\nUse `{}` instead.", suggested_command.green().bold())?;
}
}
[package] if package.name() == &from.name => {
write!(
f,
"An executable named `{}` is provided by package `{}`",
executable.cyan(),
from.name.cyan(),
)?;
}
[package] => {
let suggested_command = format!(
"{invocation_source} --from {} {}",
package.name(),
executable
);
write!(
f,
"An executable named `{}` is not provided by package `{}` but is available via the dependency `{}`. Consider using `{}` instead.",
executable.cyan(),
from.name.cyan(),
package.name().cyan(),
suggested_command.green()
)?;
}
packages => {
let provided_by = packages
.iter()
.map(uv_distribution_types::Name::name)
.map(|name| format!("- {}", name.cyan()))
.join("\n");
if self.not_from_expected() {
let suggested_command = format!("{invocation_source} --from PKG {executable}");
write!(
f,
"An executable named `{}` is not provided by package `{}` but is available via the following dependencies:\n- {}\nConsider using `{}` instead.",
executable.cyan(),
from.name.cyan(),
provided_by,
suggested_command.green(),
)?;
} else {
write!(
f,
"An executable named `{}` is provided by package `{}` but is also available via the following dependencies:\n- {}\nUnexpected behavior may occur.",
executable.cyan(),
from.name.cyan(),
provided_by,
)?;
}
}
}
Ok(())
}
}
#[derive(Debug)]
#[expect(clippy::large_enum_variant)]
pub(crate) enum ToolRequirement {
Python {
executable: String,
},
Package {
executable: String,
requirement: Requirement,
},
}
impl ToolRequirement {
fn executable(&self) -> &str {
match self {
Self::Python { executable, .. } => executable,
Self::Package { executable, .. } => executable,
}
}
}
impl std::fmt::Display for ToolRequirement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Python { .. } => write!(f, "python"),
Self::Package { requirement, .. } => write!(f, "{requirement}"),
}
}
}
async fn get_or_create_environment(
request: &ToolRequest<'_>,
with: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
build_constraints: &[RequirementsSource],
show_resolution: bool,
python: Option<&str>,
python_platform: Option<TargetTriple>,
install_mirrors: PythonInstallMirrors,
options: ResolverInstallerOptions,
settings: &ResolverInstallerSettings,
client_builder: &BaseClientBuilder<'_>,
isolated: bool,
lfs: GitLfsSetting,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
installer_metadata: bool,
concurrency: &Concurrency,
cache: &Cache,
workspace_cache: &WorkspaceCache,
printer: Printer,
preview: Preview,
) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> {
let reporter = PythonDownloadReporter::single(printer);
let explicit_python_request = python.map(PythonRequest::parse);
let tool_python_request = match request {
ToolRequest::Python { request, .. } => Some(request.clone()),
ToolRequest::Package { .. } => None,
};
let python_request = match (explicit_python_request, tool_python_request) {
(Some(explicit), Some(tool_request)) if tool_request != PythonRequest::Default => {
return Err(anyhow::anyhow!(
"Received multiple Python version requests: `{}` and `{}`",
explicit.to_canonical_string().cyan(),
tool_request.to_canonical_string().cyan()
)
.into());
}
(Some(explicit), _) => Some(explicit),
(None, Some(PythonRequest::Default) | None) => PythonVersionFile::discover(
&*CWD,
&VersionFileDiscoveryOptions::default()
.with_no_config(false)
.with_no_local(true),
)
.await?
.and_then(PythonVersionFile::into_version),
(None, Some(tool_request)) => Some(tool_request),
};
let interpreter = PythonInstallation::find_or_download(
python_request.as_ref(),
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
client_builder,
cache,
Some(&reporter),
install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
preview,
)
.await?
.into_interpreter();
let state = PlatformState::default();
let from = match request {
ToolRequest::Python {
executable: request_executable,
..
} => ToolRequirement::Python {
executable: request_executable.unwrap_or("python").to_string(),
},
ToolRequest::Package {
executable: request_executable,
target,
} => {
let (executable, requirement) = match target {
Target::Unspecified(requirement) => {
let spec = RequirementsSpecification::parse_package(requirement)?;
let name = match &spec.requirement {
UnresolvedRequirement::Named(..) => {
let content = requirement.trim();
let index = content
.find(|c| !matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.'))
.unwrap_or(content.len());
Some(&content[..index])
}
UnresolvedRequirement::Unnamed(..) => None,
};
let requirement = resolve_names(
vec![spec],
&interpreter,
settings,
client_builder,
&state,
concurrency,
cache,
workspace_cache,
printer,
preview,
lfs,
)
.await?
.pop()
.unwrap();
let executable = request_executable
.map(ToString::to_string)
.or_else(|| name.map(ToString::to_string))
.unwrap_or_else(|| requirement.name.to_string());
(executable, requirement)
}
Target::Version(executable, name, extras, version) => {
let executable = request_executable
.map(ToString::to_string)
.unwrap_or_else(|| (*executable).to_string());
let requirement = Requirement {
name: name.clone(),
extras: extras.clone(),
groups: Box::new([]),
marker: MarkerTree::default(),
source: RequirementSource::Registry {
specifier: VersionSpecifiers::from(VersionSpecifier::equals_version(
version.clone(),
)),
index: None,
conflict: None,
},
origin: None,
};
(executable, requirement)
}
Target::Latest(executable, name, extras) => {
let executable = request_executable
.map(ToString::to_string)
.unwrap_or_else(|| (*executable).to_string());
let requirement = Requirement {
name: name.clone(),
extras: extras.clone(),
groups: Box::new([]),
marker: MarkerTree::default(),
source: RequirementSource::Registry {
specifier: VersionSpecifiers::empty(),
index: None,
conflict: None,
},
origin: None,
};
(executable, requirement)
}
};
ToolRequirement::Package {
executable,
requirement,
}
}
};
let latest = if let ToolRequest::Package {
target: Target::Latest(_, name, _),
..
} = &request
{
let client = RegistryClientBuilder::new(
client_builder
.clone()
.keyring(settings.resolver.keyring_provider),
cache.clone(),
)
.index_locations(settings.resolver.index_locations.clone())
.index_strategy(settings.resolver.index_strategy)
.markers(interpreter.markers())
.platform(interpreter.platform())
.build()?;
let capabilities = IndexCapabilities::default();
let download_concurrency = concurrency.downloads_semaphore.clone();
let latest_client = LatestClient {
client: &client,
capabilities: &capabilities,
prerelease: settings.resolver.prerelease,
exclude_newer: &settings.resolver.exclude_newer,
index_locations: &settings.resolver.index_locations,
tags: None,
requires_python: None,
};
if let Some(dist_filename) = latest_client
.find_latest(name, None, &download_concurrency)
.await?
{
let version = dist_filename.version().clone();
debug!("Resolved `{name}@latest` to `{name}=={version}`");
Some(Requirement {
name: name.clone(),
extras: vec![].into_boxed_slice(),
groups: Box::new([]),
marker: MarkerTree::default(),
source: RequirementSource::Registry {
specifier: VersionSpecifiers::from(VersionSpecifier::equals_version(version)),
index: None,
conflict: None,
},
origin: None,
})
} else {
None
}
} else {
None
};
let spec = RequirementsSpecification::from_sources(
with,
constraints,
overrides,
&[],
None,
client_builder,
)
.await?;
let requirements = {
let mut requirements = Vec::with_capacity(1 + with.len());
match &from {
ToolRequirement::Python { .. } => {}
ToolRequirement::Package { requirement, .. } => requirements.push(requirement.clone()),
}
requirements.extend(
resolve_names(
spec.requirements.clone(),
&interpreter,
settings,
client_builder,
&state,
concurrency,
cache,
workspace_cache,
printer,
preview,
lfs,
)
.await?,
);
requirements
};
let constraints = spec
.constraints
.clone()
.into_iter()
.map(|constraint| constraint.requirement)
.collect::<Vec<_>>();
let overrides = resolve_names(
spec.overrides.clone(),
&interpreter,
settings,
client_builder,
&state,
concurrency,
cache,
workspace_cache,
printer,
preview,
lfs,
)
.await?;
if !isolated && !request.is_latest() {
let installed_tools = InstalledTools::from_settings()?.init()?;
let _lock = installed_tools.lock().await?;
if let ToolRequirement::Package { requirement, .. } = &from {
let existing_environment = installed_tools
.get_environment(&requirement.name, cache)?
.filter(|environment| {
python_request.as_ref().is_none_or(|python_request| {
python_request.satisfied(environment.environment().interpreter(), cache)
})
});
if let Some(environment) = existing_environment {
if installed_tools
.get_tool_receipt(&requirement.name)
.ok()
.flatten()
.is_some_and(|receipt| ToolOptions::from(options) == *receipt.options())
{
let ResolverInstallerSettings {
resolver:
ResolverSettings {
config_setting,
config_settings_package,
extra_build_dependencies,
extra_build_variables,
..
},
..
} = settings;
let extra_build_requires = LoweredExtraBuildDependencies::from_non_lowered(
extra_build_dependencies.clone(),
)
.into_inner();
let markers =
pip::resolution_markers(None, python_platform.as_ref(), &interpreter);
let tags = pip::resolution_tags(None, python_platform.as_ref(), &interpreter)?;
let site_packages = SitePackages::from_environment(environment.environment())?;
if matches!(
site_packages.satisfies_requirements(
requirements.iter(),
constraints.iter().chain(latest.iter()),
overrides.iter(),
InstallationStrategy::Permissive,
&markers,
&tags,
config_setting,
config_settings_package,
&extra_build_requires,
extra_build_variables,
),
Ok(SatisfiesResult::Fresh { .. })
) {
debug!("Using existing tool `{}`", requirement.name);
return Ok((from, environment.into_environment()));
}
}
}
}
}
let spec = EnvironmentSpecification::from(RequirementsSpecification {
requirements: requirements
.into_iter()
.map(UnresolvedRequirementSpecification::from)
.collect(),
constraints: constraints
.into_iter()
.chain(latest)
.map(NameRequirementSpecification::from)
.collect(),
overrides: overrides
.into_iter()
.map(UnresolvedRequirementSpecification::from)
.collect(),
..spec
});
let build_constraints = Constraints::from_requirements(
operations::read_constraints(build_constraints, client_builder)
.await?
.into_iter()
.map(|constraint| constraint.requirement),
);
let result = CachedEnvironment::from_spec(
spec.clone(),
build_constraints.clone(),
&interpreter,
python_platform.as_ref(),
settings,
client_builder,
&state,
if show_resolution {
Box::new(DefaultResolveLogger)
} else {
Box::new(SummaryResolveLogger)
},
if show_resolution {
Box::new(DefaultInstallLogger)
} else {
Box::new(SummaryInstallLogger)
},
installer_metadata,
concurrency,
cache,
workspace_cache,
printer,
preview,
)
.await;
let environment = match result {
Ok(environment) => environment,
Err(err) => match err {
ProjectError::Operation(err) => {
let Some(interpreter) = refine_interpreter(
&interpreter,
python_request.as_ref(),
&err,
client_builder,
&reporter,
&install_mirrors,
python_preference,
python_downloads,
cache,
preview,
)
.await
.ok()
.flatten() else {
return Err(err.into());
};
debug!(
"Re-resolving with Python {} (`{}`)",
interpreter.python_version(),
interpreter.sys_executable().display()
);
CachedEnvironment::from_spec(
spec,
build_constraints,
&interpreter,
python_platform.as_ref(),
settings,
client_builder,
&state,
if show_resolution {
Box::new(DefaultResolveLogger)
} else {
Box::new(SummaryResolveLogger)
},
if show_resolution {
Box::new(DefaultInstallLogger)
} else {
Box::new(SummaryInstallLogger)
},
installer_metadata,
concurrency,
cache,
workspace_cache,
printer,
preview,
)
.await?
}
err => return Err(err),
},
};
Ok((from, environment.into()))
}