uv/crates/uv/src/lib.rs

1634 lines
59 KiB
Rust

use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt::Write;
use std::io::stdout;
use std::path::Path;
use std::process::ExitCode;
use anstream::eprintln;
use anyhow::Result;
use clap::error::{ContextKind, ContextValue};
use clap::{CommandFactory, Parser};
use owo_colors::OwoColorize;
use settings::PipTreeSettings;
use tokio::task::spawn_blocking;
use tracing::{debug, instrument};
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_cli::{
compat::CompatArgs, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands,
PipCommand, PipNamespace, ProjectCommand,
};
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs};
#[cfg(feature = "self-update")]
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
use uv_fs::CWD;
use uv_requirements::RequirementsSource;
use uv_scripts::{Pep723Item, Pep723Metadata, Pep723Script};
use uv_settings::{Combine, FilesystemOptions, Options};
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, Workspace};
use crate::commands::{ExitStatus, RunCommand, ToolRunCommand};
use crate::printer::Printer;
use crate::settings::{
CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings,
PublishSettings,
};
pub(crate) mod commands;
pub(crate) mod logging;
pub(crate) mod printer;
pub(crate) mod settings;
pub(crate) mod version;
#[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 {
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(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 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(_)) {
// For commands that operate at the user-level, ignore local configuration.
FilesystemOptions::user()?
} else if let Ok(workspace) =
Workspace::discover(&project_dir, &DiscoveryOptions::default()).await
{
let project = FilesystemOptions::find(workspace.install_path())?;
let user = FilesystemOptions::user()?;
project.combine(user)
} else {
let project = FilesystemOptions::find(&project_dir)?;
let user = FilesystemOptions::user()?;
project.combine(user)
};
// 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,
..
}) = &mut **command
{
let settings = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
Some(
RunCommand::from_args(
command,
*module,
*script,
settings.connectivity,
settings.native_tls,
)
.await?,
)
} else {
None
}
} else {
None
};
// If the target is a PEP 723 script, parse it.
let script = if let Commands::Project(command) = &*cli.command {
if let ProjectCommand::Run(uv_cli::RunArgs { .. }) = &**command {
match run_command.as_ref() {
Some(
RunCommand::PythonScript(script, _) | RunCommand::PythonGuiScript(script, _),
) => Pep723Script::read(&script).await?.map(Pep723Item::Script),
Some(RunCommand::PythonRemote(script, _)) => {
Pep723Metadata::read(&script).await?.map(Pep723Item::Remote)
}
Some(RunCommand::PythonStdin(contents)) => {
Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin)
}
_ => None,
}
} else if let ProjectCommand::Remove(uv_cli::RemoveArgs {
script: Some(script),
..
}) = &**command
{
Pep723Script::read(&script).await?.map(Pep723Item::Script)
} else {
None
}
} 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);
// 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());
// Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")]
let (duration_layer, _duration_guard) = logging::setup_duration()?;
#[cfg(not(feature = "tracing-durations-export"))]
let duration_layer = None::<tracing_subscriber::layer::Identity>;
logging::setup_logging(
match globals.verbose {
0 => logging::Level::Default,
1 => logging::Level::Verbose,
2.. => logging::Level::ExtraVerbose,
},
duration_layer,
)?;
// Configure the `Printer`, which controls user-facing output in the CLI.
let printer = if globals.quiet {
Printer::Quiet
} 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 {
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("UV_NO_WRAP").map(|_| false).unwrap_or(true))
.build(),
)
}))?;
rayon::ThreadPoolBuilder::new()
.num_threads(globals.concurrency.installs)
.build_global()
.expect("failed to initialize global rayon pool");
debug!("uv {}", version::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)?;
let result = 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::<Vec<_>>();
let constraints = args
.constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let overrides = args
.r#override
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let build_constraints = args
.build_constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
commands::pip_compile(
&requirements,
&constraints,
&overrides,
&build_constraints,
args.constraints_from_workspace,
args.overrides_from_workspace,
args.environments,
args.settings.extras,
args.settings.output_file.as_deref(),
args.settings.resolution,
args.settings.prerelease,
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.dependency_metadata,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.config_setting,
globals.connectivity,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
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.native_tls,
globals.quiet,
cache,
printer,
)
.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::<Vec<_>>();
let constraints = args
.constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let build_constraints = args
.build_constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
commands::pip_sync(
&requirements,
&constraints,
&build_constraints,
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.dependency_metadata,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.allow_empty_requirements,
globals.connectivity,
&args.settings.config_setting,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
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.concurrency,
globals.native_tls,
cache,
args.dry_run,
printer,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Install(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipInstallSettings::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
.package
.into_iter()
.map(RequirementsSource::from_package)
.chain(args.editable.into_iter().map(RequirementsSource::Editable))
.chain(
args.requirement
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
let constraints = args
.constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let overrides = args
.r#override
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let build_constraints = args
.build_constraint
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
commands::pip_install(
&requirements,
&constraints,
&overrides,
&build_constraints,
args.constraints_from_workspace,
args.overrides_from_workspace,
&args.settings.extras,
args.settings.resolution,
args.settings.prerelease,
args.settings.dependency_mode,
args.settings.upgrade,
args.settings.index_locations,
args.settings.index_strategy,
args.settings.dependency_metadata,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.reinstall,
args.settings.link_mode,
args.settings.compile_bytecode,
args.settings.hash_checking,
globals.connectivity,
&args.settings.config_setting,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
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.concurrency,
globals.native_tls,
cache,
args.dry_run,
printer,
)
.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 sources = args
.package
.into_iter()
.map(RequirementsSource::from_package)
.chain(
args.requirement
.into_iter()
.map(RequirementsSource::from_requirements_txt),
)
.collect::<Vec<_>>();
commands::pip_uninstall(
&sources,
args.settings.python,
args.settings.system,
args.settings.break_system_packages,
args.settings.target,
args.settings.prefix,
cache,
globals.connectivity,
globals.native_tls,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
printer,
)
.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,
&cache,
printer,
)
}
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.settings.strict,
args.settings.python.as_deref(),
args.settings.system,
&cache,
printer,
)
}
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,
&cache,
printer,
)
}
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.shared.strict,
args.shared.python.as_deref(),
args.shared.system,
&cache,
printer,
)
}
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,
)
}
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_constraint
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
commands::build_frontend(
&project_dir,
args.src,
args.package,
args.all,
args.out_dir,
args.sdist,
args.wheel,
args.build_logs,
build_constraints,
args.hash_checking,
args.python,
args.settings,
cli.top_level.no_config,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.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()?;
// 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
}
});
commands::venv(
&project_dir,
args.path,
args.settings.python.as_deref(),
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,
args.settings.allow_insecure_host,
uv_virtualenv::Prompt::from_args(prompt),
args.system_site_packages,
globals.connectivity,
args.seed,
args.allow_existing,
args.settings.exclude_newer,
globals.concurrency,
globals.native_tls,
cli.top_level.no_config,
args.no_project,
&cache,
printer,
args.relocatable,
)
.await
}
Commands::Project(project) => {
Box::pin(run_project(
project,
&project_dir,
run_command,
script,
globals,
cli.top_level.no_config,
filesystem,
cache,
printer,
))
.await
}
#[cfg(feature = "self-update")]
Commands::Self_(SelfNamespace {
command:
SelfCommand::Update(SelfUpdateArgs {
target_version,
token,
}),
}) => commands::self_update(target_version, token, printer).await,
Commands::Version { output_format } => {
commands::version(output_format, &mut stdout())?;
Ok(ExitStatus::Success)
}
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, 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)
.version(env!("CARGO_PKG_VERSION"));
// Copy the top-level arguments into the `uvx` command. (Like `Args::augment_args`, but
// expanded to skip collisions.)
for arg in TopLevelArgs::command().get_arguments() {
if arg.get_id() != "isolated" {
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.upgrade.clone())),
);
let requirements = args
.with
.into_iter()
.map(RequirementsSource::from_with_package)
.chain(
args.with_editable
.into_iter()
.map(RequirementsSource::Editable),
)
.chain(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
commands::tool_run(
args.command,
args.from,
&requirements,
args.show_resolution || globals.verbose > 0,
args.python,
args.settings,
invocation_source,
args.isolated,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
cache,
printer,
)
.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.upgrade.clone())),
);
let requirements = args
.with
.into_iter()
.map(RequirementsSource::from_with_package)
.chain(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
Box::pin(commands::tool_install(
args.package,
args.editable,
args.from,
&requirements,
args.python,
args.force,
args.options,
args.settings,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
cache,
printer,
))
.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,
&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()));
commands::tool_upgrade(
args.name,
args.python,
globals.connectivity,
args.args,
args.filesystem,
globals.python_preference,
globals.python_downloads,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.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.kinds,
args.all_versions,
args.all_platforms,
globals.python_preference,
globals.python_downloads,
&cache,
printer,
)
.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);
show_settings!(args);
commands::python_install(
&project_dir,
args.targets,
args.reinstall,
globals.python_downloads,
globals.native_tls,
globals.connectivity,
cli.top_level.no_config,
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.targets, args.all, printer).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()?;
commands::python_find(
&project_dir,
args.request,
args.no_project,
cli.top_level.no_config,
args.system,
globals.python_preference,
&cache,
)
.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,
args.no_project,
&cache,
printer,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Dir,
}) => {
commands::python_dir()?;
Ok(ExitStatus::Success)
}
Commands::Publish(args) => {
show_settings!(args);
if globals.preview.is_disabled() {
warn_user_once!("`uv publish` is experimental and may change without warning");
}
// Resolve the settings from the command-line arguments and workspace configuration.
let PublishSettings {
files,
username,
password,
publish_url,
trusted_publishing,
keyring_provider,
allow_insecure_host,
} = PublishSettings::resolve(args, filesystem);
commands::publish(
files,
publish_url,
trusted_publishing,
keyring_provider,
allow_insecure_host,
username,
password,
globals.connectivity,
globals.native_tls,
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"),
};
result
}
/// 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,
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);
}
};
($arg:expr, false) => {
if globals.show_settings {#
writeln!(printer.stdout(), "{:#?}", $arg)?;
}
};
}
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.vcs,
args.no_readme,
args.author_from,
args.no_pin_python,
args.python,
args.no_workspace,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.native_tls,
&cache,
printer,
)
.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.upgrade.clone())),
);
let requirements = args
.with
.into_iter()
.map(RequirementsSource::from_with_package)
.chain(
args.with_editable
.into_iter()
.map(RequirementsSource::Editable),
)
.chain(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
Box::pin(commands::run(
project_dir,
script,
command,
requirements,
args.show_resolution || globals.verbose > 0,
args.locked,
args.frozen,
args.no_sync,
args.isolated,
args.package,
args.no_project,
no_config,
args.extras,
args.dev,
args.editable,
args.python,
args.settings,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
))
.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.upgrade.clone())),
);
commands::sync(
project_dir,
args.locked,
args.frozen,
args.package,
args.extras,
args.dev,
args.editable,
args.install_options,
args.modifications,
args.python,
globals.python_preference,
globals.python_downloads,
args.settings,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.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())),
);
commands::lock(
project_dir,
args.locked,
args.frozen,
args.python,
args.settings,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.await
}
ProjectCommand::Add(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::AddSettings::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
.packages
.into_iter()
.map(RequirementsSource::Package)
.chain(
args.requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
Box::pin(commands::add(
project_dir,
args.locked,
args.frozen,
args.no_sync,
requirements,
args.editable,
args.dependency_type,
args.raw_sources,
args.rev,
args.tag,
args.branch,
args.extras,
args.package,
args.python,
args.settings,
args.script,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
))
.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.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"),
});
commands::remove(
project_dir,
args.locked,
args.frozen,
args.no_sync,
args.packages,
args.dependency_type,
args.package,
args.python,
args.settings,
script,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.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()?;
commands::tree(
project_dir,
args.dev,
args.locked,
args.frozen,
args.universal,
args.depth,
args.prune,
args.package,
args.no_dedupe,
args.invert,
args.python_version,
args.python_platform,
args.python,
args.resolver,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
printer,
)
.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()?;
commands::export(
project_dir,
args.format,
args.package,
args.hashes,
args.install_options,
args.output_file,
args.extras,
args.dev,
args.editable,
args.locked,
args.frozen,
args.include_header,
args.python,
args.settings,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
globals.quiet,
&cache,
printer,
)
.await
}
}
}
/// The main entry point for a uv invocation.
///
/// WARNING: 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 pointis called multiple times in a single process.
pub fn main<I, T>(args: I) -> ExitCode
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
// `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" | "lock" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip compile".to_string()),
);
}
"sync" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip sync".to_string()),
);
}
"install" | "add" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip install".to_string()),
);
}
"uninstall" | "remove" => {
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()),
);
}
"tree" => {
err.insert(
ContextKind::SuggestedSubcommand,
ContextValue::String("uv pip tree".to_string()),
);
}
_ => {}
}
}
err.exit()
}
};
// Windows has a default stack size of 1MB, which is lower than the linux and mac default.
// https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170
// We support increasing the stack size to avoid stack overflows in debug mode on Windows. In
// addition, we box types and futures in various places. This includes the `Box::pin(run())`
// here, which prevents the large (non-send) main future alone from overflowing the stack.
let result = if let Ok(stack_size) = std::env::var("UV_STACK_SIZE") {
let stack_size = stack_size.parse().expect("Invalid stack size");
let tokio_main = move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.thread_stack_size(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
};
std::thread::Builder::new()
.stack_size(stack_size)
.spawn(tokio_main)
.expect("Tokio executor failed, was there a panic?")
.join()
.expect("Tokio executor failed, was there a panic?")
} else {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed building the Runtime");
// Box the large main future to avoid stack overflows.
let result = runtime.block_on(Box::pin(run(cli)));
runtime.shutdown_background();
result
};
match result {
Ok(code) => code.into(),
Err(err) => {
let mut causes = err.chain();
eprintln!("{}: {}", "error".red().bold(), causes.next().unwrap());
for err in causes {
eprintln!(" {}: {}", "Caused by".red().bold(), err);
}
ExitStatus::Error.into()
}
}
}