Files
uv/crates/uv/src/lib.rs
T
2025-08-19 15:32:56 +01:00

2321 lines
87 KiB
Rust

use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt::Write;
use std::io::stdout;
#[cfg(feature = "self-update")]
use std::ops::Bound;
use std::path::Path;
use std::process::ExitCode;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use anstream::eprintln;
use anyhow::{Context, Result, bail};
use clap::error::{ContextKind, ContextValue};
use clap::{CommandFactory, Parser};
use futures::FutureExt;
use owo_colors::OwoColorize;
use settings::PipTreeSettings;
use tokio::task::spawn_blocking;
use tracing::{debug, instrument, trace};
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
#[cfg(feature = "self-update")]
use uv_cli::SelfUpdateArgs;
use uv_cli::{
BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
ProjectCommand, PythonCommand, PythonNamespace, SelfCommand, SelfNamespace, ToolCommand,
ToolNamespace, TopLevelArgs, compat::CompatArgs,
};
use uv_configuration::min_stack_size;
use uv_fs::{CWD, Simplified};
#[cfg(feature = "self-update")]
use uv_pep440::release_specifiers_to_ranges;
use uv_pep508::VersionOrUrl;
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl};
use uv_python::PythonRequest;
use uv_requirements::{GroupsSpecification, RequirementsSource};
use uv_requirements_txt::RequirementsTxtRequirement;
use uv_scripts::{Pep723Error, Pep723Item, Pep723Metadata, Pep723Script};
use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options};
use uv_static::EnvVars;
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache};
use crate::commands::{ExitStatus, RunCommand, ScriptPath, ToolRunCommand};
use crate::printer::Printer;
use crate::settings::{
CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings,
PublishSettings,
};
pub(crate) mod child;
pub(crate) mod commands;
pub(crate) mod logging;
pub(crate) mod printer;
pub(crate) mod settings;
#[cfg(windows)]
mod windows_exception;
#[instrument(skip_all)]
async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Enable flag to pick up warnings generated by workspace loading.
if cli.top_level.global_args.quiet == 0 {
uv_warnings::enable();
}
// Switch directories as early as possible.
if let Some(directory) = cli.top_level.global_args.directory.as_ref() {
std::env::set_current_dir(directory)?;
}
// Determine the project directory.
let project_dir = cli
.top_level
.global_args
.project
.as_deref()
.map(std::path::absolute)
.transpose()?
.map(uv_fs::normalize_path_buf)
.map(Cow::Owned)
.unwrap_or_else(|| Cow::Borrowed(&*CWD));
// The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs.
let deprecated_isolated = if cli.top_level.global_args.isolated {
match &*cli.command {
// Supports `--isolated` as its own argument, so we can't warn either way.
Commands::Tool(ToolNamespace {
command: ToolCommand::Uvx(_) | ToolCommand::Run(_),
}) => false,
// Supports `--isolated` as its own argument, so we can't warn either way.
Commands::Project(command) if matches!(**command, ProjectCommand::Run(_)) => false,
// `--isolated` moved to `--no-workspace`.
Commands::Project(command) if matches!(**command, ProjectCommand::Init(_)) => {
warn_user!(
"The `--isolated` flag is deprecated and has no effect. Instead, use `--no-config` to prevent uv from discovering configuration files or `--no-workspace` to prevent uv from adding the initialized project to the containing workspace."
);
false
}
// Preview APIs. Ignore `--isolated` and warn.
Commands::Project(_) | Commands::Tool(_) | Commands::Python(_) => {
warn_user!(
"The `--isolated` flag is deprecated and has no effect. Instead, use `--no-config` to prevent uv from discovering configuration files."
);
false
}
// Non-preview APIs. Continue to support `--isolated`, but warn.
_ => {
warn_user!(
"The `--isolated` flag is deprecated. Instead, use `--no-config` to prevent uv from discovering configuration files."
);
true
}
}
} else {
false
};
// Load configuration from the filesystem, prioritizing (in order):
// 1. The configuration file specified on the command-line.
// 2. The nearest configuration file (`uv.toml` or `pyproject.toml`) above the workspace root.
// If found, this file is combined with the user configuration file.
// 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree,
// starting from the current directory.
let workspace_cache = WorkspaceCache::default();
let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() {
if config_file
.file_name()
.is_some_and(|file_name| file_name == "pyproject.toml")
{
warn_user!(
"The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--project` argument instead."
);
}
Some(FilesystemOptions::from_file(config_file)?)
} else if deprecated_isolated || cli.top_level.no_config {
None
} else if matches!(&*cli.command, Commands::Tool(_) | Commands::Self_(_)) {
// For commands that operate at the user-level, ignore local configuration.
FilesystemOptions::user()?.combine(FilesystemOptions::system()?)
} else if let Ok(workspace) =
Workspace::discover(&project_dir, &DiscoveryOptions::default(), &workspace_cache).await
{
let project = FilesystemOptions::find(workspace.install_path())?;
let system = FilesystemOptions::system()?;
let user = FilesystemOptions::user()?;
project.combine(user).combine(system)
} else {
let project = FilesystemOptions::find(&project_dir)?;
let system = FilesystemOptions::system()?;
let user = FilesystemOptions::user()?;
project.combine(user).combine(system)
};
// Parse the external command, if necessary.
let run_command = if let Commands::Project(command) = &mut *cli.command {
if let ProjectCommand::Run(uv_cli::RunArgs {
command: Some(command),
module,
script,
gui_script,
..
}) = &mut **command
{
let settings = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
Some(
RunCommand::from_args(
command,
settings.network_settings,
*module,
*script,
*gui_script,
)
.await?,
)
} else {
None
}
} else {
None
};
// If the target is a PEP 723 script, parse it.
let script = if let Commands::Project(command) = &*cli.command {
match &**command {
ProjectCommand::Run(uv_cli::RunArgs { .. }) => match run_command.as_ref() {
Some(
RunCommand::PythonScript(script, _) | RunCommand::PythonGuiScript(script, _),
) => match Pep723Script::read(&script).await {
Ok(Some(script)) => Some(Pep723Item::Script(script)),
Ok(None) => None,
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(err.into()),
},
Some(RunCommand::PythonRemote(url, script, _)) => {
match Pep723Metadata::read(&script).await {
Ok(Some(metadata)) => Some(Pep723Item::Remote(metadata, url.clone())),
Ok(None) => None,
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
None
}
Err(err) => return Err(err.into()),
}
}
Some(
RunCommand::PythonStdin(contents, _) | RunCommand::PythonGuiStdin(contents, _),
) => Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin),
_ => None,
},
// For `uv add --script` and `uv lock --script`, we'll create a PEP 723 tag if it
// doesn't already exist.
ProjectCommand::Add(uv_cli::AddArgs {
script: Some(script),
..
})
| ProjectCommand::Lock(uv_cli::LockArgs {
script: Some(script),
..
}) => match Pep723Script::read(&script).await {
Ok(Some(script)) => Some(Pep723Item::Script(script)),
Ok(None) => None,
Err(err) => return Err(err.into()),
},
// For the remaining commands, the PEP 723 tag must exist already.
ProjectCommand::Remove(uv_cli::RemoveArgs {
script: Some(script),
..
})
| ProjectCommand::Sync(uv_cli::SyncArgs {
script: Some(script),
..
})
| ProjectCommand::Tree(uv_cli::TreeArgs {
script: Some(script),
..
})
| ProjectCommand::Export(uv_cli::ExportArgs {
script: Some(script),
..
}) => match Pep723Script::read(&script).await {
Ok(Some(script)) => Some(Pep723Item::Script(script)),
Ok(None) => {
bail!(
"`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
bail!(
"Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(err) => return Err(err.into()),
},
_ => None,
}
} else if let Commands::Python(uv_cli::PythonNamespace {
command:
PythonCommand::Find(uv_cli::PythonFindArgs {
script: Some(script),
..
}),
}) = &*cli.command
{
match Pep723Script::read(&script).await {
Ok(Some(script)) => Some(Pep723Item::Script(script)),
Ok(None) => {
bail!(
"`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
bail!(
"Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(err) => return Err(err.into()),
}
} else {
None
};
// If the target is a PEP 723 script, merge the metadata into the filesystem metadata.
let filesystem = script
.as_ref()
.map(Pep723Item::metadata)
.and_then(|metadata| metadata.tool.as_ref())
.and_then(|tool| tool.uv.as_ref())
.map(|uv| Options::simple(uv.globals.clone(), uv.top_level.clone()))
.map(FilesystemOptions::from)
.combine(filesystem);
// Load environment variables not handled by Clap
let environment = EnvironmentOptions::new()?;
// Resolve the global settings.
let globals = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
// Resolve the cache settings.
let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref());
// Enforce the required version.
if let Some(required_version) = globals.required_version.as_ref() {
let package_version = uv_pep440::Version::from_str(uv_version::version())?;
if !required_version.contains(&package_version) {
#[cfg(feature = "self-update")]
let hint = {
// If the required version range includes a lower bound that's higher than
// the current version, suggest `uv self update`.
let ranges = release_specifiers_to_ranges(required_version.specifiers().clone());
if let Some(singleton) = ranges.as_singleton() {
// E.g., `==1.0.0`
format!(
". Update `uv` by running `{}`.",
format!("uv self update {singleton}").green()
)
} else if ranges
.bounding_range()
.iter()
.any(|(lowest, _highest)| match lowest {
Bound::Included(version) => **version > package_version,
Bound::Excluded(version) => **version > package_version,
Bound::Unbounded => false,
})
{
// E.g., `>=1.0.0`
format!(". Update `uv` by running `{}`.", "uv self update".cyan())
} else {
String::new()
}
};
#[cfg(not(feature = "self-update"))]
let hint = "";
return Err(anyhow::anyhow!(
"Required uv version `{required_version}` does not match the running version `{package_version}`{hint}",
));
}
}
// Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")]
let (durations_layer, _duration_guard) = logging::setup_durations()?;
#[cfg(not(feature = "tracing-durations-export"))]
let durations_layer = None::<tracing_subscriber::layer::Identity>;
logging::setup_logging(
match globals.verbose {
0 => logging::Level::Off,
1 => logging::Level::DebugUv,
2 => logging::Level::TraceUv,
3.. => logging::Level::TraceAll,
},
durations_layer,
globals.color,
)?;
// Configure the `Printer`, which controls user-facing output in the CLI.
let printer = if globals.quiet == 1 {
Printer::Quiet
} else if globals.quiet > 1 {
Printer::Silent
} else if globals.verbose > 0 {
Printer::Verbose
} else if globals.no_progress {
Printer::NoProgress
} else {
Printer::Default
};
// Configure the `warn!` macros, which control user-facing warnings in the CLI.
if globals.quiet > 0 {
uv_warnings::disable();
} else {
uv_warnings::enable();
}
anstream::ColorChoice::write_global(globals.color.into());
miette::set_hook(Box::new(|_| {
Box::new(
miette::MietteHandlerOpts::new()
.break_words(false)
.word_separator(textwrap::WordSeparator::AsciiSpace)
.word_splitter(textwrap::WordSplitter::NoHyphenation)
.wrap_lines(
std::env::var(EnvVars::UV_NO_WRAP)
.map(|_| false)
.unwrap_or(true),
)
.build(),
)
}))?;
// Don't initialize the rayon threadpool yet, this is too costly when we're doing a noop sync.
uv_configuration::RAYON_PARALLELISM.store(globals.concurrency.installs, Ordering::Relaxed);
debug!("uv {}", uv_cli::version::uv_self_version());
// Write out any resolved settings.
macro_rules! show_settings {
($arg:expr) => {
if globals.show_settings {
writeln!(printer.stdout(), "{:#?}", $arg)?;
return Ok(ExitStatus::Success);
}
};
($arg:expr, false) => {
if globals.show_settings {
writeln!(printer.stdout(), "{:#?}", $arg)?;
}
};
}
show_settings!(globals, false);
show_settings!(cache_settings, false);
// Configure the cache.
let cache = Cache::from_settings(cache_settings.no_cache, cache_settings.cache_dir)?;
match *cli.command {
Commands::Help(args) => commands::help(
args.command.unwrap_or_default().as_slice(),
printer,
args.no_pager,
),
Commands::Pip(PipNamespace {
command: PipCommand::Compile(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCompileSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.upgrade.clone())),
);
let requirements = args
.src_file
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?;
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let overrides = args
.overrides
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Result<Vec<_>, _>>()?;
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let groups = GroupsSpecification {
root: project_dir.to_path_buf(),
groups: args.settings.groups,
};
commands::pip_compile(
&requirements,
&constraints,
&overrides,
&build_constraints,
args.constraints_from_workspace,
args.overrides_from_workspace,
args.build_constraints_from_workspace,
args.environments,
args.settings.extras,
groups,
args.settings.output_file.as_deref(),
args.format,
args.settings.resolution,
args.settings.prerelease,
args.settings.fork_strategy,
args.settings.dependency_mode,
args.settings.upgrade,
args.settings.generate_hashes,
args.settings.no_emit_package,
args.settings.no_strip_extras,
args.settings.no_strip_markers,
!args.settings.no_annotate,
!args.settings.no_header,
args.settings.custom_compile_command,
args.settings.emit_index_url,
args.settings.emit_find_links,
args.settings.emit_build_options,
args.settings.emit_marker_expression,
args.settings.emit_index_annotation,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.torch_backend,
args.settings.dependency_metadata,
args.settings.keyring_provider,
&globals.network_settings,
args.settings.config_setting,
args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.settings.python_version,
args.settings.python_platform,
args.settings.universal,
args.settings.exclude_newer,
args.settings.sources,
args.settings.annotation_style,
args.settings.link_mode,
args.settings.python,
args.settings.system,
globals.python_preference,
globals.concurrency,
globals.quiet > 0,
cache,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Sync(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipSyncSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.upgrade.clone())),
);
let requirements = args
.src_file
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?;
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let groups = GroupsSpecification {
root: project_dir.to_path_buf(),
groups: args.settings.groups,
};
commands::pip_sync(
&requirements,
&constraints,
&build_constraints,
&args.settings.extras,
&groups,
args.settings.reinstall,
args.settings.link_mode,
args.settings.compile_bytecode,
args.settings.hash_checking,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.torch_backend,
args.settings.dependency_metadata,
args.settings.keyring_provider,
&globals.network_settings,
args.settings.allow_empty_requirements,
globals.installer_metadata,
&args.settings.config_setting,
&args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.settings.python_version,
args.settings.python_platform,
args.settings.strict,
args.settings.exclude_newer,
args.settings.python,
args.settings.system,
args.settings.break_system_packages,
args.settings.target,
args.settings.prefix,
args.settings.sources,
globals.python_preference,
globals.concurrency,
cache,
args.dry_run,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Install(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let mut args = PipInstallSettings::resolve(args, filesystem);
show_settings!(args);
let mut requirements = Vec::with_capacity(
args.package.len() + args.editables.len() + args.requirements.len(),
);
for package in args.package {
requirements.push(RequirementsSource::from_package_argument(&package)?);
}
for package in args.editables {
requirements.push(RequirementsSource::from_editable(&package)?);
}
requirements.extend(
args.requirements
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?,
);
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let overrides = args
.overrides
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Result<Vec<_>, _>>()?;
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Result<Vec<_>, _>>()?;
let groups = GroupsSpecification {
root: project_dir.to_path_buf(),
groups: args.settings.groups,
};
// Special-case: any source trees specified on the command-line are automatically
// reinstalled. This matches user expectations: `uv pip install .` should always
// re-build and re-install the package in the current working directory.
for requirement in &requirements {
let requirement = match requirement {
RequirementsSource::Package(requirement) => requirement,
RequirementsSource::Editable(requirement) => requirement,
_ => continue,
};
match requirement {
RequirementsTxtRequirement::Named(requirement) => {
if let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() {
if let ParsedUrl::Directory(ParsedDirectoryUrl {
install_path, ..
}) = &url.parsed_url
{
debug!(
"Marking explicit source tree for reinstall: `{}`",
install_path.display()
);
args.settings.reinstall = args
.settings
.reinstall
.with_package(requirement.name.clone());
}
}
}
RequirementsTxtRequirement::Unnamed(requirement) => {
if let ParsedUrl::Directory(ParsedDirectoryUrl { install_path, .. }) =
&requirement.url.parsed_url
{
debug!(
"Marking explicit source tree for reinstall: `{}`",
install_path.display()
);
args.settings.reinstall =
args.settings.reinstall.with_path(install_path.clone());
}
}
}
}
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.upgrade.clone())),
);
commands::pip_install(
&requirements,
&constraints,
&overrides,
&build_constraints,
args.constraints_from_workspace,
args.overrides_from_workspace,
args.build_constraints_from_workspace,
&args.settings.extras,
&groups,
args.settings.resolution,
args.settings.prerelease,
args.settings.dependency_mode,
args.settings.upgrade,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.torch_backend,
args.settings.dependency_metadata,
args.settings.keyring_provider,
&globals.network_settings,
args.settings.reinstall,
args.settings.link_mode,
args.settings.compile_bytecode,
args.settings.hash_checking,
globals.installer_metadata,
&args.settings.config_setting,
&args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.modifications,
args.settings.python_version,
args.settings.python_platform,
args.settings.strict,
args.settings.exclude_newer,
args.settings.sources,
args.settings.python,
args.settings.system,
args.settings.break_system_packages,
args.settings.target,
args.settings.prefix,
globals.python_preference,
globals.concurrency,
cache,
args.dry_run,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Uninstall(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipUninstallSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
let mut sources = Vec::with_capacity(args.package.len() + args.requirements.len());
for package in args.package {
sources.push(RequirementsSource::from_package_argument(&package)?);
}
sources.extend(
args.requirements
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?,
);
commands::pip_uninstall(
&sources,
args.settings.python,
args.settings.system,
args.settings.break_system_packages,
args.settings.target,
args.settings.prefix,
cache,
args.settings.keyring_provider,
&globals.network_settings,
args.dry_run,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Freeze(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipFreezeSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_freeze(
args.exclude_editable,
args.settings.strict,
args.settings.python.as_deref(),
args.settings.system,
args.paths,
&cache,
printer,
globals.preview,
)
}
Commands::Pip(PipNamespace {
command: PipCommand::List(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipListSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_list(
args.editable,
&args.exclude,
&args.format,
args.outdated,
args.settings.prerelease,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
&globals.network_settings,
globals.concurrency,
args.settings.strict,
args.settings.exclude_newer,
args.settings.python.as_deref(),
args.settings.system,
&cache,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Show(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipShowSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_show(
args.package,
args.settings.strict,
args.settings.python.as_deref(),
args.settings.system,
args.files,
&cache,
printer,
globals.preview,
)
}
Commands::Pip(PipNamespace {
command: PipCommand::Tree(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipTreeSettings::resolve(args, filesystem);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_tree(
args.show_version_specifiers,
args.depth,
&args.prune,
&args.package,
args.no_dedupe,
args.invert,
args.outdated,
args.settings.prerelease,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
globals.network_settings,
globals.concurrency,
args.settings.strict,
args.settings.exclude_newer,
args.settings.python.as_deref(),
args.settings.system,
&cache,
printer,
globals.preview,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Check(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCheckSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_check(
args.settings.python.as_deref(),
args.settings.system,
&cache,
printer,
globals.preview,
)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Clean(args),
})
| Commands::Clean(args) => {
show_settings!(args);
commands::cache_clean(&args.package, &cache, printer)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Prune(args),
}) => {
show_settings!(args);
commands::cache_prune(args.ci, &cache, printer)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Dir,
}) => {
commands::cache_dir(&cache);
Ok(ExitStatus::Success)
}
Commands::Build(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::BuildSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.upgrade.clone())),
);
// Resolve the build constraints.
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
commands::build_frontend(
&project_dir,
args.src,
args.package,
args.all_packages,
args.out_dir,
args.sdist,
args.wheel,
args.list,
args.build_logs,
args.force_pep517,
build_constraints,
args.hash_checking,
args.python,
args.install_mirrors,
&args.settings,
&globals.network_settings,
cli.top_level.no_config,
globals.python_preference,
globals.python_downloads,
globals.concurrency,
&cache,
printer,
globals.preview,
)
.await
}
Commands::Venv(args) => {
args.compat_args.validate()?;
if args.no_system {
warn_user_once!(
"The `--no-system` flag has no effect, a system Python interpreter is always used in `uv venv`"
);
}
if args.system {
warn_user_once!(
"The `--system` flag has no effect, a system Python interpreter is always used in `uv venv`"
);
}
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::VenvSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.upgrade.clone())),
);
// Since we use ".venv" as the default name, we use "." as the default prompt.
let prompt = args.prompt.or_else(|| {
if args.path.is_none() {
Some(".".to_string())
} else {
None
}
});
let python_request: Option<PythonRequest> =
args.settings.python.as_deref().map(PythonRequest::parse);
let on_existing = uv_virtualenv::OnExisting::from_args(args.allow_existing, args.clear);
commands::venv(
&project_dir,
args.path,
python_request,
args.settings.install_mirrors,
globals.python_preference,
globals.python_downloads,
args.settings.link_mode,
&args.settings.index_locations,
args.settings.index_strategy,
args.settings.dependency_metadata,
args.settings.keyring_provider,
&globals.network_settings,
uv_virtualenv::Prompt::from_args(prompt),
args.system_site_packages,
args.seed,
on_existing,
args.settings.exclude_newer,
globals.concurrency,
cli.top_level.no_config,
args.no_project,
&cache,
printer,
args.relocatable,
globals.preview,
)
.await
}
Commands::Project(project) => {
Box::pin(run_project(
project,
&project_dir,
run_command,
script,
globals,
cli.top_level.no_config,
cli.top_level.global_args.project.is_some(),
filesystem,
cache,
printer,
))
.await
}
#[cfg(feature = "self-update")]
Commands::Self_(SelfNamespace {
command:
SelfCommand::Update(SelfUpdateArgs {
target_version,
token,
dry_run,
}),
}) => {
commands::self_update(
target_version,
token,
dry_run,
printer,
globals.network_settings,
)
.await
}
Commands::Self_(SelfNamespace {
command:
SelfCommand::Version {
short,
output_format,
},
}) => {
commands::self_version(short, output_format, printer)?;
Ok(ExitStatus::Success)
}
#[cfg(not(feature = "self-update"))]
Commands::Self_(_) => {
anyhow::bail!(
"uv was installed through an external package manager, and self-update \
is not available. Please use your package manager to update uv."
);
}
Commands::GenerateShellCompletion(args) => {
args.shell.generate(&mut Cli::command(), &mut stdout());
Ok(ExitStatus::Success)
}
Commands::Tool(ToolNamespace {
command: run_variant @ (ToolCommand::Uvx(_) | ToolCommand::Run(_)),
}) => {
let (args, invocation_source) = match run_variant {
ToolCommand::Uvx(args) => (args.tool_run, ToolRunCommand::Uvx),
ToolCommand::Run(args) => (args, ToolRunCommand::ToolRun),
// OK guarded by the outer match statement
_ => unreachable!(),
};
if let Some(shell) = args.generate_shell_completion {
// uvx: combine `uv tool uvx` with the top-level arguments
let mut uvx = Cli::command()
.find_subcommand("tool")
.unwrap()
.find_subcommand("uvx")
.unwrap()
.clone()
// Avoid duplicating the `--help` and `--version` flags from the top-level
// arguments.
.disable_help_flag(true)
.disable_version_flag(true);
// Copy the top-level arguments into the `uvx` command, as in `Args::augment_args`,
// but expanded to skip collisions.
for arg in TopLevelArgs::command().get_arguments() {
if arg.get_id() != "isolated" && arg.get_id() != "version" {
uvx = uvx.arg(arg);
}
}
shell.generate(&mut uvx, &mut stdout());
return Ok(ExitStatus::Success);
}
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolRunSettings::resolve(args, filesystem, invocation_source);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
let requirements = {
let mut requirements = Vec::with_capacity(
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
);
for package in args.with {
requirements.push(RequirementsSource::from_with_package_argument(&package)?);
}
for package in args.with_editable {
requirements.push(RequirementsSource::from_editable(&package)?);
}
requirements.extend(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?,
);
requirements
};
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let overrides = args
.overrides
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Result<Vec<_>, _>>()?;
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
Box::pin(commands::tool_run(
args.command,
args.from,
&requirements,
&constraints,
&overrides,
&build_constraints,
args.show_resolution || globals.verbose > 0,
args.python,
args.install_mirrors,
args.options,
args.settings,
globals.network_settings,
invocation_source,
args.isolated,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
cache,
printer,
args.env_file,
args.no_env_file,
globals.preview,
))
.await
}
Commands::Tool(ToolNamespace {
command: ToolCommand::Install(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolInstallSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
let mut entrypoints = Vec::with_capacity(args.with_executables_from.len());
let mut requirements = Vec::with_capacity(
args.with.len()
+ args.with_editable.len()
+ args.with_requirements.len()
+ args.with_executables_from.len(),
);
for pkg in args.with {
requirements.push(RequirementsSource::from_with_package_argument(&pkg)?);
}
for pkg in args.with_editable {
requirements.push(RequirementsSource::from_editable(&pkg)?);
}
for path in args.with_requirements {
requirements.push(RequirementsSource::from_requirements_file(path)?);
}
for pkg in &args.with_executables_from {
let source = RequirementsSource::from_with_package_argument(pkg)?;
let RequirementsSource::Package(RequirementsTxtRequirement::Named(requirement)) =
&source
else {
bail!(
"Expected a named package for `--with-executables-from`, but got: {}",
source.to_string().cyan()
)
};
entrypoints.push(requirement.name.clone());
requirements.push(source);
}
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
let overrides = args
.overrides
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Result<Vec<_>, _>>()?;
let build_constraints = args
.build_constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
Box::pin(commands::tool_install(
args.package,
args.editable,
args.from,
&requirements,
&constraints,
&overrides,
&build_constraints,
&entrypoints,
args.python,
args.install_mirrors,
args.force,
args.options,
args.settings,
globals.network_settings,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
cache,
printer,
globals.preview,
))
.await
}
Commands::Tool(ToolNamespace {
command: ToolCommand::List(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolListSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::tool_list(
args.show_paths,
args.show_version_specifiers,
args.show_with,
args.show_extras,
&cache,
printer,
)
.await
}
Commands::Tool(ToolNamespace {
command: ToolCommand::Upgrade(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolUpgradeSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(Refresh::All(Timestamp::now()));
Box::pin(commands::tool_upgrade(
args.names,
args.python,
args.install_mirrors,
args.args,
args.filesystem,
globals.network_settings,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
&cache,
printer,
globals.preview,
))
.await
}
Commands::Tool(ToolNamespace {
command: ToolCommand::Uninstall(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolUninstallSettings::resolve(args, filesystem);
show_settings!(args);
commands::tool_uninstall(args.name, printer).await
}
Commands::Tool(ToolNamespace {
command: ToolCommand::UpdateShell,
}) => {
commands::tool_update_shell(printer).await?;
Ok(ExitStatus::Success)
}
Commands::Tool(ToolNamespace {
command: ToolCommand::Dir(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolDirSettings::resolve(args, filesystem);
show_settings!(args);
commands::tool_dir(args.bin, globals.preview)?;
Ok(ExitStatus::Success)
}
Commands::Python(PythonNamespace {
command: PythonCommand::List(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonListSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::python_list(
args.request,
args.kinds,
args.all_versions,
args.all_platforms,
args.all_arches,
args.show_urls,
args.output_format,
args.python_downloads_json_url,
globals.python_preference,
globals.python_downloads,
&cache,
printer,
globals.preview,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Install(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
show_settings!(args);
// TODO(john): If we later want to support `--upgrade`, we need to replace this.
let upgrade = false;
commands::python_install(
&project_dir,
args.install_dir,
args.targets,
args.reinstall,
upgrade,
args.bin,
args.registry,
args.force,
args.python_install_mirror,
args.pypy_install_mirror,
args.python_downloads_json_url,
globals.network_settings,
args.default,
globals.python_downloads,
cli.top_level.no_config,
globals.preview,
printer,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Upgrade(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonUpgradeSettings::resolve(args, filesystem);
show_settings!(args);
let upgrade = true;
commands::python_install(
&project_dir,
args.install_dir,
args.targets,
args.reinstall,
upgrade,
args.bin,
args.registry,
args.force,
args.python_install_mirror,
args.pypy_install_mirror,
args.python_downloads_json_url,
globals.network_settings,
args.default,
globals.python_downloads,
cli.top_level.no_config,
globals.preview,
printer,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Uninstall(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonUninstallSettings::resolve(args, filesystem);
show_settings!(args);
commands::python_uninstall(
args.install_dir,
args.targets,
args.all,
printer,
globals.preview,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Find(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonFindSettings::resolve(args, filesystem);
// Initialize the cache.
let cache = cache.init()?;
if let Some(Pep723Item::Script(script)) = script {
commands::python_find_script(
(&script).into(),
args.show_version,
&globals.network_settings,
globals.python_preference,
globals.python_downloads,
cli.top_level.no_config,
&cache,
printer,
globals.preview,
)
.await
} else {
commands::python_find(
&project_dir,
args.request,
args.show_version,
args.no_project,
cli.top_level.no_config,
args.system,
globals.python_preference,
&cache,
printer,
globals.preview,
)
.await
}
}
Commands::Python(PythonNamespace {
command: PythonCommand::Pin(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonPinSettings::resolve(args, filesystem);
// Initialize the cache.
let cache = cache.init()?;
commands::python_pin(
&project_dir,
args.request,
args.resolved,
globals.python_preference,
globals.python_downloads,
args.no_project,
args.global,
args.rm,
args.install_mirrors,
globals.network_settings,
&cache,
printer,
globals.preview,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Dir(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::PythonDirSettings::resolve(args, filesystem);
show_settings!(args);
commands::python_dir(args.bin)?;
Ok(ExitStatus::Success)
}
Commands::Python(PythonNamespace {
command: PythonCommand::UpdateShell,
}) => {
commands::python_update_shell(printer).await?;
Ok(ExitStatus::Success)
}
Commands::Publish(args) => {
show_settings!(args);
if args.skip_existing {
bail!(
"`uv publish` does not support `--skip-existing` because there is not a \
reliable way to identify when an upload fails due to an existing \
distribution. Instead, use `--check-url` to provide the URL to the simple \
API for your index. uv will check the index for existing distributions before \
attempting uploads."
);
}
// Resolve the settings from the command-line arguments and workspace configuration.
let PublishSettings {
files,
username,
password,
publish_url,
trusted_publishing,
keyring_provider,
check_url,
index,
index_locations,
} = PublishSettings::resolve(args, filesystem);
let (publish_url, check_url) = if let Some(index_name) = index {
debug!("Publishing with index {index_name}");
let index = index_locations
.simple_indexes()
.find(|index| {
index
.name
.as_ref()
.is_some_and(|name| name.as_ref() == index_name)
})
.with_context(|| {
let mut index_names: Vec<String> = index_locations
.simple_indexes()
.filter_map(|index| index.name.as_ref())
.map(ToString::to_string)
.collect();
index_names.sort();
if index_names.is_empty() {
format!("No indexes were found, can't use index: `{index_name}`")
} else {
let index_names = index_names.join("`, `");
format!(
"Index not found: `{index_name}`. Found indexes: `{index_names}`"
)
}
})?;
let publish_url = index
.publish_url
.clone()
.with_context(|| format!("Index is missing a publish URL: `{index_name}`"))?;
let check_url = index.url.clone();
(publish_url, Some(check_url))
} else {
(publish_url, check_url)
};
commands::publish(
files,
publish_url,
trusted_publishing,
keyring_provider,
&globals.network_settings,
username,
password,
check_url,
&cache,
printer,
)
.await
}
Commands::BuildBackend { command } => spawn_blocking(move || match command {
BuildBackendCommand::BuildSdist { sdist_directory } => {
commands::build_backend::build_sdist(&sdist_directory)
}
BuildBackendCommand::BuildWheel {
wheel_directory,
metadata_directory,
} => commands::build_backend::build_wheel(
&wheel_directory,
metadata_directory.as_deref(),
),
BuildBackendCommand::BuildEditable {
wheel_directory,
metadata_directory,
} => commands::build_backend::build_editable(
&wheel_directory,
metadata_directory.as_deref(),
),
BuildBackendCommand::GetRequiresForBuildSdist => {
commands::build_backend::get_requires_for_build_sdist()
}
BuildBackendCommand::GetRequiresForBuildWheel => {
commands::build_backend::get_requires_for_build_wheel()
}
BuildBackendCommand::PrepareMetadataForBuildWheel { wheel_directory } => {
commands::build_backend::prepare_metadata_for_build_wheel(&wheel_directory)
}
BuildBackendCommand::GetRequiresForBuildEditable => {
commands::build_backend::get_requires_for_build_editable()
}
BuildBackendCommand::PrepareMetadataForBuildEditable { wheel_directory } => {
commands::build_backend::prepare_metadata_for_build_editable(&wheel_directory)
}
})
.await
.expect("tokio threadpool exited unexpectedly"),
}
}
/// Run a [`ProjectCommand`].
async fn run_project(
project_command: Box<ProjectCommand>,
project_dir: &Path,
command: Option<RunCommand>,
script: Option<Pep723Item>,
globals: GlobalSettings,
// TODO(zanieb): Determine a better story for passing `no_config` in here
no_config: bool,
explicit_project: bool,
filesystem: Option<FilesystemOptions>,
cache: Cache,
printer: Printer,
) -> Result<ExitStatus> {
// Write out any resolved settings.
macro_rules! show_settings {
($arg:expr) => {
if globals.show_settings {
writeln!(printer.stdout(), "{:#?}", $arg)?;
return Ok(ExitStatus::Success);
}
};
}
match *project_command {
ProjectCommand::Init(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::InitSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
commands::init(
project_dir,
args.path,
args.name,
args.package,
args.kind,
args.bare,
args.description,
args.no_description,
args.vcs,
args.build_backend,
args.no_readme,
args.author_from,
args.pin_python,
args.python,
args.install_mirrors,
args.no_workspace,
&globals.network_settings,
globals.python_preference,
globals.python_downloads,
no_config,
&cache,
printer,
globals.preview,
)
.await
}
ProjectCommand::Run(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::RunSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
let mut requirements = Vec::with_capacity(
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
);
for package in args.with {
requirements.push(RequirementsSource::from_with_package_argument(&package)?);
}
for package in args.with_editable {
requirements.push(RequirementsSource::from_editable(&package)?);
}
requirements.extend(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file)
.collect::<Result<Vec<_>, _>>()?,
);
Box::pin(commands::run(
project_dir,
script,
command,
requirements,
args.show_resolution || globals.verbose > 0,
args.locked,
args.frozen,
args.active,
args.no_sync,
args.isolated,
args.all_packages,
args.package,
args.no_project,
no_config,
args.extras,
args.groups,
args.editable,
args.modifications,
args.python,
args.install_mirrors,
args.settings,
globals.network_settings,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
&cache,
printer,
args.env_file,
args.no_env_file,
globals.preview,
args.max_recursion_depth,
))
.await
}
ProjectCommand::Sync(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::SyncSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
// Unwrap the script.
let script = script.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv lock` does not support stdin"),
Pep723Item::Remote(..) => unreachable!("`uv lock` does not support remote files"),
});
Box::pin(commands::sync(
project_dir,
args.locked,
args.frozen,
args.dry_run,
args.active,
args.all_packages,
args.package,
args.extras,
args.groups,
args.editable,
args.install_options,
args.modifications,
args.python,
args.python_platform,
args.install_mirrors,
globals.python_preference,
globals.python_downloads,
args.settings,
globals.network_settings,
script,
globals.installer_metadata,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
args.output_format,
))
.await
}
ProjectCommand::Lock(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::LockSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.upgrade.clone())),
);
// If the script already exists, use it; otherwise, propagate the file path and we'll
// initialize it later on.
let script = script
.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv add` does not support stdin"),
Pep723Item::Remote(..) => {
unreachable!("`uv add` does not support remote files")
}
})
.map(ScriptPath::Script)
.or(args.script.map(ScriptPath::Path));
Box::pin(commands::lock(
project_dir,
args.locked,
args.frozen,
args.dry_run,
args.python,
args.install_mirrors,
args.settings,
globals.network_settings,
script,
globals.python_preference,
globals.python_downloads,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
))
.await
}
ProjectCommand::Add(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let mut args = settings::AddSettings::resolve(args, filesystem);
show_settings!(args);
// If the script already exists, use it; otherwise, propagate the file path and we'll
// initialize it later on.
let script = script
.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv add` does not support stdin"),
Pep723Item::Remote(..) => {
unreachable!("`uv add` does not support remote files")
}
})
.map(ScriptPath::Script)
.or(args.script.map(ScriptPath::Path));
let requirements = args
.packages
.iter()
.map(String::as_str)
.map(RequirementsSource::from_package_argument)
.chain(
args.requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Result<Vec<_>>>()?;
// Special-case: any local source trees specified on the command-line are automatically
// reinstalled.
for requirement in &requirements {
let requirement = match requirement {
RequirementsSource::Package(requirement) => requirement,
RequirementsSource::Editable(requirement) => requirement,
_ => continue,
};
match requirement {
RequirementsTxtRequirement::Named(requirement) => {
if let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() {
if let ParsedUrl::Directory(ParsedDirectoryUrl {
install_path, ..
}) = &url.parsed_url
{
debug!(
"Marking explicit source tree for reinstall: `{}`",
install_path.display()
);
args.settings.reinstall = args
.settings
.reinstall
.with_package(requirement.name.clone());
}
}
}
RequirementsTxtRequirement::Unnamed(requirement) => {
if let ParsedUrl::Directory(ParsedDirectoryUrl { install_path, .. }) =
&requirement.url.parsed_url
{
debug!(
"Marking explicit source tree for reinstall: `{}`",
install_path.display()
);
args.settings.reinstall =
args.settings.reinstall.with_path(install_path.clone());
}
}
}
}
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Result<Vec<_>, _>>()?;
Box::pin(commands::add(
project_dir,
args.locked,
args.frozen,
args.active,
args.no_sync,
args.no_install_project,
args.no_install_workspace,
requirements,
constraints,
args.marker,
args.editable,
args.dependency_type,
args.raw,
args.bounds,
args.indexes,
args.rev,
args.tag,
args.branch,
args.extras,
args.package,
args.python,
args.workspace,
args.install_mirrors,
args.settings,
globals.network_settings,
script,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
))
.await
}
ProjectCommand::Remove(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::RemoveSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
// Unwrap the script.
let script = script.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv remove` does not support stdin"),
Pep723Item::Remote(..) => unreachable!("`uv remove` does not support remote files"),
});
Box::pin(commands::remove(
project_dir,
args.locked,
args.frozen,
args.active,
args.no_sync,
args.packages,
args.dependency_type,
args.package,
args.python,
args.install_mirrors,
args.settings,
globals.network_settings,
script,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
))
.await
}
ProjectCommand::Version(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::VersionSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.resolver.upgrade.clone())),
);
Box::pin(commands::project_version(
args.value,
args.bump,
args.short,
args.output_format,
project_dir,
args.package,
explicit_project,
args.dry_run,
args.locked,
args.frozen,
args.active,
args.no_sync,
args.python,
args.install_mirrors,
args.settings,
globals.network_settings,
globals.python_preference,
globals.python_downloads,
globals.installer_metadata,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
))
.await
}
ProjectCommand::Tree(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::TreeSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
// Unwrap the script.
let script = script.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv tree` does not support stdin"),
Pep723Item::Remote(..) => unreachable!("`uv tree` does not support remote files"),
});
Box::pin(commands::tree(
project_dir,
args.groups,
args.locked,
args.frozen,
args.universal,
args.depth,
args.prune,
args.package,
args.no_dedupe,
args.invert,
args.outdated,
args.python_version,
args.python_platform,
args.python,
args.install_mirrors,
args.resolver,
&globals.network_settings,
script,
globals.python_preference,
globals.python_downloads,
globals.concurrency,
no_config,
&cache,
printer,
globals.preview,
))
.await
}
ProjectCommand::Export(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ExportSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache.
let cache = cache.init()?;
// Unwrap the script.
let script = script.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(..) => unreachable!("`uv export` does not support stdin"),
Pep723Item::Remote(..) => unreachable!("`uv export` does not support remote files"),
});
commands::export(
project_dir,
args.format,
args.all_packages,
args.package,
args.prune,
args.hashes,
args.install_options,
args.output_file,
args.extras,
args.groups,
args.editable,
args.locked,
args.frozen,
args.include_annotations,
args.include_header,
script,
args.python,
args.install_mirrors,
args.settings,
globals.network_settings,
globals.python_preference,
globals.python_downloads,
globals.concurrency,
no_config,
globals.quiet > 0,
&cache,
printer,
globals.preview,
)
.boxed_local()
.await
}
}
}
/// The main entry point for a uv invocation.
///
/// # Usage
///
/// This entry point is not recommended for external consumption, the uv binary interface is the
/// official public API.
///
/// When using this entry point, uv assumes it is running in a process it controls and that the
/// entire process lifetime is managed by uv. Unexpected behavior may be encountered if this entry
/// point is called multiple times in a single process.
///
/// # Safety
///
/// It is only safe to call this routine when it is known that multiple threads are not running.
#[allow(unsafe_code)]
pub unsafe fn main<I, T>(args: I) -> ExitCode
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
#[cfg(windows)]
windows_exception::setup();
// Set the `UV` variable to the current executable so it is implicitly propagated to all child
// processes, e.g., in `uv run`.
if let Ok(current_exe) = std::env::current_exe() {
// SAFETY: The proof obligation must be satisfied by the caller.
unsafe {
// This will become unsafe in Rust 2024
// See https://doc.rust-lang.org/std/env/fn.set_var.html#safety
std::env::set_var(EnvVars::UV, current_exe);
}
}
// `std::env::args` is not `Send` so we parse before passing to our runtime
// https://github.com/rust-lang/rust/pull/48005
let cli = match Cli::try_parse_from(args) {
Ok(cli) => cli,
Err(mut err) => {
if let Some(ContextValue::String(subcommand)) = err.get(ContextKind::InvalidSubcommand)
{
match subcommand.as_str() {
"compile" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip compile".to_string()),
);
}
"install" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip install".to_string()),
);
}
"uninstall" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip uninstall".to_string()),
);
}
"freeze" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip freeze".to_string()),
);
}
"list" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip list".to_string()),
);
}
"show" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip show".to_string()),
);
}
_ => {}
}
}
err.exit()
}
};
// See `min_stack_size` doc comment about `main2`
let min_stack_size = min_stack_size();
let main2 = move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.thread_stack_size(min_stack_size)
.build()
.expect("Failed building the Runtime");
// Box the large main future to avoid stack overflows.
let result = runtime.block_on(Box::pin(run(cli)));
// Avoid waiting for pending tasks to complete.
//
// The resolver may have kicked off HTTP requests during resolution that
// turned out to be unnecessary. Waiting for those to complete can cause
// the CLI to hang before exiting.
runtime.shutdown_background();
result
};
let result = std::thread::Builder::new()
.name("main2".to_owned())
.stack_size(min_stack_size)
.spawn(main2)
.expect("Tokio executor failed, was there a panic?")
.join()
.expect("Tokio executor failed, was there a panic?");
match result {
Ok(code) => code.into(),
Err(err) => {
trace!("Error trace: {err:?}");
let mut causes = err.chain();
eprintln!(
"{}: {}",
"error".red().bold(),
causes.next().unwrap().to_string().trim()
);
for err in causes {
eprintln!(" {}: {}", "Caused by".red().bold(), err.to_string().trim());
}
ExitStatus::Error.into()
}
}
}