Skip to main content

uv/
lib.rs

1#![deny(clippy::print_stdout, clippy::print_stderr)]
2
3use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fmt::Write;
6use std::io::stdout;
7#[cfg(feature = "self-update")]
8use std::ops::Bound;
9use std::path::Path;
10use std::process::ExitCode;
11use std::str::FromStr;
12use std::sync::atomic::Ordering;
13
14use anyhow::{Result, anyhow, bail};
15use clap::error::{ContextKind, ContextValue};
16use clap::{CommandFactory, Error, Parser};
17use futures::FutureExt;
18use owo_colors::OwoColorize;
19use settings::PipTreeSettings;
20use tokio::task::spawn_blocking;
21use tracing::{debug, instrument, trace};
22
23#[cfg(not(feature = "self-update"))]
24use crate::install_source::InstallSource;
25use uv_cache::{Cache, Refresh};
26use uv_cache_info::Timestamp;
27#[cfg(feature = "self-update")]
28use uv_cli::SelfUpdateArgs;
29use uv_cli::{
30    AuthCommand, AuthHelperCommand, AuthNamespace, BuildBackendCommand, CacheCommand,
31    CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand, PythonCommand,
32    PythonNamespace, SelfCommand, SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs,
33    WorkspaceCommand, WorkspaceNamespace, compat::CompatArgs,
34};
35use uv_client::BaseClientBuilder;
36use uv_configuration::min_stack_size;
37use uv_flags::EnvironmentFlags;
38use uv_fs::{CWD, Simplified, normalize_path};
39#[cfg(feature = "self-update")]
40use uv_pep440::release_specifiers_to_ranges;
41use uv_pep508::VersionOrUrl;
42use uv_preview::PreviewFeature;
43use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl};
44use uv_python::PythonRequest;
45use uv_requirements::{GroupsSpecification, RequirementsSource};
46use uv_requirements_txt::RequirementsTxtRequirement;
47use uv_scripts::{Pep723Error, Pep723Item, Pep723Script};
48use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options};
49use uv_static::EnvVars;
50use uv_warnings::{warn_user, warn_user_once};
51use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache};
52
53use crate::commands::{
54    ExitStatus, ParsedRunCommand, RunCommand, ScriptPath, ToolRunCommand, UvError,
55};
56use crate::printer::Printer;
57use crate::settings::{
58    CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
59    PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings,
60    PublishSettings, resolve_color,
61};
62
63pub(crate) mod child;
64pub mod commands;
65#[cfg(not(feature = "self-update"))]
66mod install_source;
67mod logging;
68pub(crate) mod printer;
69pub(crate) mod settings;
70
71/// Whether to initialize process-global state.
72#[derive(Debug, Copy, Clone, Eq, PartialEq)]
73#[doc(hidden)]
74pub enum GlobalInitialization {
75    /// Initialize process-global state for the first uv invocation in this process.
76    Initialize,
77    /// Reuse process-global state, which has to be initialized by an earlier invocation.
78    Reuse,
79}
80
81impl GlobalInitialization {
82    const fn needs_initialization(self) -> bool {
83        matches!(self, Self::Initialize)
84    }
85}
86
87/// uv was installed through an external package manager and cannot update itself.
88#[cfg(not(feature = "self-update"))]
89#[derive(Debug, thiserror::Error)]
90#[error("uv was installed through an external package manager and cannot update itself.")]
91struct ExternallyInstalledError {
92    install_source: Option<InstallSource>,
93}
94
95#[cfg(not(feature = "self-update"))]
96impl uv_errors::Hint for ExternallyInstalledError {
97    fn hints(&self) -> uv_errors::Hints<'_> {
98        if let Some(source) = &self.install_source {
99            uv_errors::Hints::from(format!(
100                "You installed uv using {}. To update uv, run `{}`",
101                source.description(),
102                source.update_instructions(),
103            ))
104        } else {
105            uv_errors::Hints::from("Please use your package manager to update uv")
106        }
107    }
108}
109
110#[instrument(skip_all)]
111#[doc(hidden)]
112pub async fn run(cli: Cli, global_initialization: GlobalInitialization) -> Result<ExitStatus> {
113    // Enable flag to pick up warnings generated by workspace loading.
114    if cli.top_level.global_args.quiet == 0 {
115        uv_warnings::enable();
116    }
117
118    // Respect `UV_WORKING_DIRECTORY` for backwards compatibility.
119    let directory =
120        cli.top_level.global_args.directory.clone().or_else(|| {
121            std::env::var_os(EnvVars::UV_WORKING_DIRECTORY).map(std::path::PathBuf::from)
122        });
123
124    // Switch directories as early as possible.
125    if let Some(directory) = directory.as_ref() {
126        std::env::set_current_dir(directory)?;
127    }
128
129    // Parse the external command, if necessary.
130    let parsed_run_command = if let Commands::Project(command) = &*cli.command
131        && let ProjectCommand::Run(uv_cli::RunArgs {
132            command: Some(ref command),
133            module,
134            script,
135            gui_script,
136            ..
137        }) = **command
138    {
139        Some(ParsedRunCommand::from_args(
140            command, module, script, gui_script,
141        )?)
142    } else {
143        None
144    };
145
146    // Load environment variables not handled by Clap.
147    let environment = EnvironmentOptions::new()?;
148
149    // Resolve preview flags before config discovery for decisions that affect the discovery root.
150    let early_preview = settings::resolve_preview(&cli.top_level.global_args, None, &environment);
151
152    if global_initialization.needs_initialization() {
153        // Make the early preview flags globally available.
154        uv_preview::set(early_preview)?;
155    }
156
157    if global_initialization.needs_initialization() {
158        // Configure the `tracing` crate, which controls internal logging.
159        #[cfg(feature = "tracing-durations-export")]
160        let (durations_layer, _duration_guard) =
161            logging::setup_durations(environment.tracing_durations_file.as_ref())?;
162        #[cfg(not(feature = "tracing-durations-export"))]
163        let durations_layer = None::<tracing_subscriber::layer::Identity>;
164        logging::setup_logging(
165            match cli.top_level.global_args.verbose {
166                0 => logging::Level::Off,
167                1 => logging::Level::DebugUv,
168                2 => logging::Level::TraceUv,
169                3.. => logging::Level::TraceAll,
170            },
171            durations_layer,
172            resolve_color(&cli.top_level.global_args),
173            environment.log_context.unwrap_or_default(),
174        )?;
175    }
176
177    // Determine the project directory.
178    //
179    // If `--project` points to a `pyproject.toml` file, resolve to its parent directory,
180    // since downstream code (e.g., `FilesystemOptions::find`) expects a directory.
181    let project_dir: Cow<'_, Path> = if let Some(project) = &cli.top_level.global_args.project {
182        let path = normalize_path(std::path::absolute(project)?);
183        if let Some(name) = path.file_name()
184            && name == "pyproject.toml"
185            && path.is_file()
186            && let Some(parent) = path.parent()
187        {
188            Cow::Owned(parent.to_path_buf())
189        } else {
190            path
191        }
192    } else if let Some(run_command) = &parsed_run_command
193        && early_preview.is_enabled(PreviewFeature::TargetWorkspaceDiscovery)
194        && let Some(dir) = run_command.script_dir()
195    {
196        // When running a target with the preview flag enabled, discover the workspace starting
197        // from the target's directory rather than the current working directory.
198        Cow::Owned(std::path::absolute(dir)?)
199    } else {
200        Cow::Borrowed(&*CWD)
201    };
202
203    // Validate that the project directory exists if explicitly provided via --project, except for
204    // `uv init`, which creates the project directory (separate deprecation).
205    let skip_project_validation = matches!(
206        &*cli.command,
207        Commands::Project(command) if matches!(**command, ProjectCommand::Init(_))
208    );
209
210    if !skip_project_validation {
211        if let Some(project_path) = cli.top_level.global_args.project.as_ref() {
212            if !project_dir.exists() {
213                if early_preview.is_enabled(PreviewFeature::ProjectDirectoryMustExist) {
214                    bail!(
215                        "Project directory `{}` does not exist",
216                        project_path.user_display()
217                    );
218                }
219                warn_user_once!(
220                    "Project directory `{}` does not exist. \
221                    This will become an error in a future release. \
222                    Use `--preview-features project-directory-must-exist` to error on this now.",
223                    project_path.user_display()
224                );
225            } else if !project_dir.is_dir() {
226                // `--project path/to/pyproject.toml` is resolved to its parent above,
227                // so this only triggers for other file types (see #18508).
228                if early_preview.is_enabled(PreviewFeature::ProjectDirectoryMustExist) {
229                    bail!(
230                        "Project path `{}` is not a directory",
231                        project_path.user_display()
232                    );
233                }
234                warn_user_once!(
235                    "Project path `{}` is not a directory. \
236                    This will become an error in a future release. \
237                    Use `--preview-features project-directory-must-exist` to error on this now.",
238                    project_path.user_display()
239                );
240            }
241        }
242    }
243
244    // The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs.
245    let deprecated_isolated = if cli.top_level.global_args.isolated {
246        match &*cli.command {
247            // Supports `--isolated` as its own argument, so we can't warn either way.
248            Commands::Tool(ToolNamespace {
249                command: ToolCommand::Uvx(_) | ToolCommand::Run(_),
250            }) => false,
251
252            // Supports `--isolated` as its own argument, so we can't warn either way.
253            Commands::Project(command)
254                if matches!(**command, ProjectCommand::Run(_) | ProjectCommand::Check(_)) =>
255            {
256                false
257            }
258
259            // `--isolated` moved to `--no-workspace`.
260            Commands::Project(command) if matches!(**command, ProjectCommand::Init(_)) => {
261                warn_user!(
262                    "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."
263                );
264                false
265            }
266
267            // Preview APIs. Ignore `--isolated` and warn.
268            Commands::Project(_) | Commands::Tool(_) | Commands::Python(_) => {
269                warn_user!(
270                    "The `--isolated` flag is deprecated and has no effect. Instead, use `--no-config` to prevent uv from discovering configuration files."
271                );
272                false
273            }
274
275            // Non-preview APIs. Continue to support `--isolated`, but warn.
276            _ => {
277                warn_user!(
278                    "The `--isolated` flag is deprecated. Instead, use `--no-config` to prevent uv from discovering configuration files."
279                );
280                true
281            }
282        }
283    } else {
284        false
285    };
286
287    // Load configuration from the filesystem, prioritizing (in order):
288    // 1. The configuration file specified on the command-line.
289    // 2. The nearest configuration file (`uv.toml` or `pyproject.toml`) above the workspace root.
290    //    If found, this file is combined with the user configuration file.
291    // 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree,
292    //    starting from the current directory.
293
294    // Pass the (possibly non-existent) cache dir path to the initial workspace discovery.
295    let discovery_cache = Cache::from_settings(
296        cli.top_level.cache_args.no_cache,
297        cli.top_level.cache_args.cache_dir.clone(),
298    )?;
299    let workspace_cache = WorkspaceCache::default();
300    let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() {
301        if config_file
302            .file_name()
303            .is_some_and(|file_name| file_name == "pyproject.toml")
304        {
305            warn_user!(
306                "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."
307            );
308        }
309        Some(FilesystemOptions::from_file(config_file).map_err(map_settings_error)?)
310    } else if deprecated_isolated || cli.top_level.no_config {
311        None
312    } else if matches!(&*cli.command, Commands::Tool(_) | Commands::Self_(_)) {
313        // For commands that operate at the user-level, ignore local configuration.
314        FilesystemOptions::user()
315            .map_err(map_settings_error)?
316            .combine(FilesystemOptions::system().map_err(map_settings_error)?)
317    } else if let Ok(workspace) = Workspace::discover(
318        &project_dir,
319        &DiscoveryOptions::default(),
320        &discovery_cache,
321        &workspace_cache,
322    )
323    .await
324    {
325        let project =
326            FilesystemOptions::find(workspace.install_path()).map_err(map_settings_error)?;
327        let system = FilesystemOptions::system().map_err(map_settings_error)?;
328        let user = FilesystemOptions::user().map_err(map_settings_error)?;
329        project.combine(user).combine(system)
330    } else {
331        let project = FilesystemOptions::find(&project_dir).map_err(map_settings_error)?;
332        let system = FilesystemOptions::system().map_err(map_settings_error)?;
333        let user = FilesystemOptions::user().map_err(map_settings_error)?;
334        project.combine(user).combine(system)
335    };
336
337    // If the target is a remote script, download it.
338    // If the target is a PEP 723 script, parse it.
339    let (run_script, run_command) = if let Some(parsed_run_command) = parsed_run_command {
340        let (script, run_command) = parsed_run_command
341            .resolve(
342                &cli.top_level.global_args,
343                filesystem.as_ref(),
344                &environment,
345            )
346            .await?;
347        (script, Some(run_command))
348    } else {
349        (None, None)
350    };
351    let script = if let Some(run_script) = run_script {
352        Some(run_script)
353    } else if let Commands::Project(command) = &*cli.command {
354        match &**command {
355            // For `uv add --script` and `uv lock --script`, we'll create a PEP 723 tag if it
356            // doesn't already exist.
357            ProjectCommand::Add(uv_cli::AddArgs {
358                script: Some(script),
359                ..
360            })
361            | ProjectCommand::Lock(uv_cli::LockArgs {
362                script: Some(script),
363                ..
364            }) => match Pep723Script::read(script).await {
365                Ok(Some(script)) => Some(Pep723Item::Script(script)),
366                Ok(None) => None,
367                Err(err) => return Err(err.into()),
368            },
369            // For the remaining commands, the PEP 723 tag must exist already.
370            ProjectCommand::Remove(uv_cli::RemoveArgs {
371                script: Some(script),
372                ..
373            })
374            | ProjectCommand::Sync(uv_cli::SyncArgs {
375                script: Some(script),
376                ..
377            })
378            | ProjectCommand::Tree(uv_cli::TreeArgs {
379                script: Some(script),
380                ..
381            })
382            | ProjectCommand::Export(uv_cli::ExportArgs {
383                script: Some(script),
384                ..
385            })
386            | ProjectCommand::Audit(uv_cli::AuditArgs {
387                script: Some(script),
388                ..
389            })
390            | ProjectCommand::Check(uv_cli::CheckArgs {
391                script: Some(script),
392                ..
393            }) => match Pep723Script::read(script).await {
394                Ok(Some(script)) => Some(Pep723Item::Script(script)),
395                Ok(None) => {
396                    bail!(
397                        "`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
398                        script.user_display().cyan(),
399                        format!("uv init --script {}", script.user_display()).green()
400                    )
401                }
402                Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
403                    bail!(
404                        "Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
405                        script.user_display().cyan(),
406                        format!("uv init --script {}", script.user_display()).green()
407                    )
408                }
409                Err(err) => return Err(err.into()),
410            },
411            _ => None,
412        }
413    } else if let Commands::Workspace(WorkspaceNamespace {
414        command: WorkspaceCommand::Metadata(args),
415    }) = &*cli.command
416        && let Some(script) = args.script.as_ref()
417    {
418        match Pep723Script::read(script).await {
419            Ok(Some(script)) => Some(Pep723Item::Script(script)),
420            Ok(None) => {
421                bail!(
422                    "`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
423                    script.user_display().cyan(),
424                    format!("uv init --script {}", script.user_display()).green()
425                )
426            }
427            Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
428                bail!(
429                    "Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
430                    script.user_display().cyan(),
431                    format!("uv init --script {}", script.user_display()).green()
432                )
433            }
434            Err(err) => return Err(err.into()),
435        }
436    } else if let Commands::Python(uv_cli::PythonNamespace {
437        command:
438            PythonCommand::Find(uv_cli::PythonFindArgs {
439                script: Some(script),
440                ..
441            }),
442    }) = &*cli.command
443    {
444        match Pep723Script::read(&script).await {
445            Ok(Some(script)) => Some(Pep723Item::Script(script)),
446            Ok(None) => {
447                bail!(
448                    "`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
449                    script.user_display().cyan(),
450                    format!("uv init --script {}", script.user_display()).green()
451                )
452            }
453            Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
454                bail!(
455                    "Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
456                    script.user_display().cyan(),
457                    format!("uv init --script {}", script.user_display()).green()
458                )
459            }
460            Err(err) => return Err(err.into()),
461        }
462    } else {
463        None
464    };
465
466    // If the target is a PEP 723 script, merge the metadata into the filesystem metadata.
467    let filesystem = script
468        .as_ref()
469        .map(Pep723Item::metadata)
470        .and_then(|metadata| metadata.tool.as_ref())
471        .and_then(|tool| tool.uv.as_ref())
472        .map(|uv| Options::simple(uv.globals.clone(), uv.top_level.clone()))
473        .map(FilesystemOptions::from)
474        .combine(filesystem);
475
476    // Resolve the global settings.
477    let globals = GlobalSettings::resolve(
478        &cli.top_level.global_args,
479        filesystem.as_ref(),
480        &environment,
481    );
482
483    if global_initialization.needs_initialization() {
484        // Set the global flags.
485        uv_flags::init(EnvironmentFlags::from(&environment))
486            .map_err(|()| anyhow::anyhow!("Flags are already initialized"))?;
487    }
488
489    debug!("uv {}", uv_cli::version::uv_self_version());
490    if let Some(config_file) = cli.top_level.config_file.as_ref() {
491        debug!("Using configuration file: {}", config_file.user_display());
492    }
493    if globals.preview.all_enabled() {
494        debug!("All preview features are enabled");
495    } else if globals.preview.any_enabled() {
496        debug!(
497            "The following preview features are enabled: {}",
498            globals.preview
499        );
500    }
501
502    // Adjust open file limits on Unix if the preview feature is enabled.
503    #[cfg(unix)]
504    if global_initialization.needs_initialization()
505        && globals.preview.is_enabled(PreviewFeature::AdjustUlimit)
506    {
507        match uv_unix::adjust_open_file_limit() {
508            Ok(_) | Err(uv_unix::OpenFileLimitError::AlreadySufficient { .. }) => {}
509            // TODO(zanieb): When moving out of preview, consider changing this to a log instead of
510            // a warning because it's okay if we fail here.
511            Err(err) => warn_user!("{err}"),
512        }
513    }
514
515    // Resolve the cache settings.
516    let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref());
517
518    if global_initialization.needs_initialization() {
519        // Set and finalize the global preview configuration.
520        uv_preview::set(globals.preview)?;
521        uv_preview::finalize()?;
522    }
523
524    // Enforce the required version.
525    if let Some(required_version) = globals.required_version.as_ref() {
526        let package_version = uv_pep440::Version::from_str(uv_version::version())?;
527        if !required_version.contains(&package_version) {
528            return Err(required_version_error(required_version, &package_version));
529        }
530    }
531
532    // Configure the `Printer`, which controls user-facing output in the CLI.
533    let printer = Printer::new(globals.quiet, globals.verbose, globals.no_progress);
534
535    // Configure the `warn!` macros, which control user-facing warnings in the CLI.
536    if globals.quiet > 0 {
537        uv_warnings::disable();
538    } else {
539        uv_warnings::enable();
540    }
541
542    anstream::ColorChoice::write_global(globals.color.into());
543
544    if global_initialization.needs_initialization() {
545        miette::set_hook(Box::new(|_| {
546            Box::new(
547                miette::MietteHandlerOpts::new()
548                    .break_words(false)
549                    .word_separator(textwrap::WordSeparator::AsciiSpace)
550                    .word_splitter(textwrap::WordSplitter::NoHyphenation)
551                    .wrap_lines(std::env::var(EnvVars::UV_NO_WRAP).is_err())
552                    .build(),
553            )
554        }))?;
555    }
556
557    // Don't initialize the rayon threadpool yet, this is too costly when we're doing a noop sync.
558    uv_configuration::RAYON_PARALLELISM.store(globals.concurrency.installs, Ordering::Relaxed);
559
560    // Write out any resolved settings.
561    macro_rules! show_settings {
562        ($arg:expr) => {
563            if globals.show_settings {
564                writeln!(printer.stdout(), "{:#?}", $arg)?;
565                return Ok(ExitStatus::Success);
566            }
567        };
568        ($arg:expr, false) => {
569            if globals.show_settings {
570                writeln!(printer.stdout(), "{:#?}", $arg)?;
571            }
572        };
573    }
574    show_settings!(globals, false);
575    show_settings!(cache_settings, false);
576
577    // Configure the cache.
578    if cache_settings.no_cache {
579        debug!("Disabling the uv cache due to `--no-cache`");
580    }
581    let cache = Cache::from_settings(cache_settings.no_cache, cache_settings.cache_dir)?;
582    // This check happens after the first (fallible) workspace discovery, which we need to resolve
583    // the settings that go into the cache constructor, but the check happens before the first
584    // workspace discovery that's used beyond settings discovery.
585    let cache_dir = std::path::absolute(cache.root())?;
586    // PEP 517 hooks run from uv-managed source trees, including source distributions extracted
587    // into the cache, and can invoke uv recursively.
588    let project_is_in_build_dir =
589        std::env::var_os(EnvVars::UV_INTERNAL__BUILD_DIR).is_some_and(|build_dir| {
590            std::path::absolute(build_dir).is_ok_and(|build_dir| {
591                project_dir.starts_with(&build_dir)
592                    || fs_err::canonicalize(&*project_dir).is_ok_and(|project_dir| {
593                        fs_err::canonicalize(build_dir)
594                            .is_ok_and(|build_dir| project_dir.starts_with(build_dir))
595                    })
596            })
597        });
598    if !project_is_in_build_dir {
599        if project_dir.starts_with(&cache_dir) {
600            bail!(
601                "The project directory `{}` is inside the cache directory `{}`",
602                project_dir.user_display(),
603                cache_dir.user_display()
604            );
605        }
606        if let Ok(cache_dir) = fs_err::canonicalize(&cache_dir)
607            && let Ok(project_dir) = fs_err::canonicalize(&*project_dir)
608            && project_dir.starts_with(&cache_dir)
609        {
610            bail!(
611                "The project directory `{}` is inside the cache directory `{}`",
612                project_dir.user_display(),
613                cache_dir.user_display()
614            );
615        }
616    }
617
618    let workspace_cache = WorkspaceCache::default();
619
620    // Configure the global network settings.
621    let client_builder = BaseClientBuilder::new(
622        globals.network_settings.connectivity,
623        globals.network_settings.system_certs,
624        globals.network_settings.allow_insecure_host.clone(),
625        globals.preview,
626        globals.network_settings.read_timeout,
627        globals.network_settings.connect_timeout,
628        globals.network_settings.retries,
629    )
630    .http_proxy(globals.network_settings.http_proxy.clone())
631    .https_proxy(globals.network_settings.https_proxy.clone())
632    .no_proxy(globals.network_settings.no_proxy.clone());
633
634    match *cli.command {
635        Commands::Auth(AuthNamespace {
636            command: AuthCommand::Login(args),
637        }) => {
638            // Resolve the settings from the command-line arguments and workspace configuration.
639            let args = settings::AuthLoginSettings::resolve(args);
640            show_settings!(args);
641
642            commands::auth_login(
643                args.service,
644                args.username,
645                args.password,
646                args.token,
647                client_builder,
648                printer,
649                globals.preview,
650            )
651            .await
652        }
653        Commands::Auth(AuthNamespace {
654            command: AuthCommand::Logout(args),
655        }) => {
656            // Resolve the settings from the command-line arguments and workspace configuration.
657            let args = settings::AuthLogoutSettings::resolve(args);
658            show_settings!(args);
659
660            commands::auth_logout(
661                args.service,
662                args.username,
663                client_builder,
664                printer,
665                globals.preview,
666            )
667            .await
668        }
669        Commands::Auth(AuthNamespace {
670            command: AuthCommand::Token(args),
671        }) => {
672            // Resolve the settings from the command-line arguments and workspace configuration.
673            let args = settings::AuthTokenSettings::resolve(args);
674            show_settings!(args);
675
676            commands::auth_token(
677                args.service,
678                args.username,
679                client_builder,
680                printer,
681                globals.preview,
682            )
683            .await
684        }
685        Commands::Auth(AuthNamespace {
686            command: AuthCommand::Dir(args),
687        }) => {
688            commands::auth_dir(args.service.as_ref(), printer)?;
689            Ok(ExitStatus::Success)
690        }
691        Commands::Auth(AuthNamespace {
692            command: AuthCommand::Helper(args),
693        }) => {
694            use uv_cli::AuthHelperProtocol;
695
696            // Validate protocol (currently only Bazel is supported)
697            match args.protocol {
698                AuthHelperProtocol::Bazel => {}
699            }
700
701            match args.command {
702                AuthHelperCommand::Get => {
703                    commands::auth_helper(client_builder, globals.preview, printer).await
704                }
705            }
706        }
707        Commands::Help(args) => commands::help(
708            args.command.unwrap_or_default().as_slice(),
709            printer,
710            args.no_pager,
711        ),
712        Commands::Pip(PipNamespace {
713            command: PipCommand::Compile(args),
714        }) => {
715            args.compat_args.validate()?;
716
717            // Resolve the settings from the command-line arguments and workspace configuration.
718            let args = PipCompileSettings::resolve(args, filesystem, environment);
719            show_settings!(args);
720
721            // Check for conflicts between offline and refresh.
722            globals
723                .network_settings
724                .check_refresh_conflict(&args.refresh);
725
726            // Initialize the cache.
727            let cache = cache.init().await?.with_refresh(
728                args.refresh
729                    .combine(Refresh::from(args.settings.reinstall.clone()))
730                    .combine(Refresh::from(args.settings.upgrade.clone())),
731            );
732
733            let requirements = args
734                .src_file
735                .into_iter()
736                .map(RequirementsSource::from_requirements_file)
737                .collect::<Result<Vec<_>, _>>()?;
738            let constraints = args
739                .constraints
740                .into_iter()
741                .map(RequirementsSource::from_constraints_txt)
742                .collect::<Result<Vec<_>, _>>()?;
743            let overrides = args
744                .overrides
745                .into_iter()
746                .map(RequirementsSource::from_overrides_txt)
747                .collect::<Result<Vec<_>, _>>()?;
748            let excludes = args
749                .excludes
750                .into_iter()
751                .map(RequirementsSource::from_requirements_txt)
752                .collect::<Result<Vec<_>, _>>()?;
753            let build_constraints = args
754                .build_constraints
755                .into_iter()
756                .map(RequirementsSource::from_constraints_txt)
757                .collect::<Result<Vec<_>, _>>()?;
758            let groups = GroupsSpecification {
759                root: project_dir.to_path_buf(),
760                groups: args.settings.groups,
761            };
762
763            Box::pin(commands::pip_compile(
764                &requirements,
765                &constraints,
766                &overrides,
767                &excludes,
768                &build_constraints,
769                args.constraints_from_workspace,
770                args.overrides_from_workspace,
771                args.excludes_from_workspace,
772                args.build_constraints_from_workspace,
773                args.environments,
774                args.required_environments,
775                args.settings.extras,
776                groups,
777                args.settings.output_file.as_deref(),
778                args.format,
779                args.settings.resolution,
780                args.settings.prerelease,
781                args.settings.fork_strategy,
782                args.settings.dependency_mode,
783                args.settings.upgrade,
784                args.settings.generate_hashes,
785                args.settings.no_emit_package,
786                args.settings.no_strip_extras,
787                args.settings.no_strip_markers,
788                !args.settings.no_annotate,
789                !args.settings.no_header,
790                args.settings.custom_compile_command,
791                args.settings.emit_index_url,
792                args.settings.emit_find_links,
793                args.settings.emit_build_options,
794                args.settings.emit_marker_expression,
795                args.settings.emit_index_annotation,
796                args.settings.index_locations,
797                args.settings.index_strategy,
798                args.settings.torch_backend,
799                args.settings.cuda_driver_version,
800                args.settings.amd_gpu_architecture,
801                args.settings.dependency_metadata,
802                args.settings.keyring_provider,
803                &client_builder.subcommand(vec!["pip".to_owned(), "compile".to_owned()]),
804                args.settings.config_setting,
805                args.settings.config_settings_package,
806                args.settings.build_isolation.clone(),
807                &args.settings.extra_build_dependencies,
808                &args.settings.extra_build_variables,
809                args.settings.build_options,
810                args.settings.install_mirrors,
811                args.settings.python_version,
812                args.settings.python_platform,
813                globals.python_downloads,
814                args.settings.universal,
815                args.settings.exclude_newer,
816                args.settings.sources,
817                args.settings.annotation_style,
818                args.settings.link_mode,
819                args.settings.python,
820                args.settings.system,
821                globals.python_preference,
822                globals.concurrency,
823                globals.quiet > 0,
824                cache,
825                workspace_cache,
826                printer,
827                globals.preview,
828            ))
829            .await
830        }
831        Commands::Pip(PipNamespace {
832            command: PipCommand::Sync(args),
833        }) => {
834            args.compat_args.validate()?;
835
836            // Resolve the settings from the command-line arguments and workspace configuration.
837            let args = PipSyncSettings::resolve(args, filesystem, environment);
838            show_settings!(args);
839
840            // Check for conflicts between offline and refresh.
841            globals
842                .network_settings
843                .check_refresh_conflict(&args.refresh);
844
845            // Initialize the cache.
846            let cache = cache.init().await?.with_refresh(
847                args.refresh
848                    .combine(Refresh::from(args.settings.reinstall.clone()))
849                    .combine(Refresh::from(args.settings.upgrade.clone())),
850            );
851
852            let requirements = args
853                .src_file
854                .into_iter()
855                .map(RequirementsSource::from_requirements_file)
856                .collect::<Result<Vec<_>, _>>()?;
857            let constraints = args
858                .constraints
859                .into_iter()
860                .map(RequirementsSource::from_constraints_txt)
861                .collect::<Result<Vec<_>, _>>()?;
862            let build_constraints = args
863                .build_constraints
864                .into_iter()
865                .map(RequirementsSource::from_constraints_txt)
866                .collect::<Result<Vec<_>, _>>()?;
867            let groups = GroupsSpecification {
868                root: project_dir.to_path_buf(),
869                groups: args.settings.groups,
870            };
871
872            Box::pin(commands::pip_sync(
873                &requirements,
874                &constraints,
875                &build_constraints,
876                &args.settings.extras,
877                &groups,
878                args.settings.reinstall,
879                args.settings.link_mode,
880                args.settings.compile_bytecode,
881                args.settings.hash_checking,
882                args.settings.index_locations,
883                args.settings.index_strategy,
884                args.settings.torch_backend,
885                args.settings.cuda_driver_version,
886                args.settings.amd_gpu_architecture,
887                args.settings.dependency_metadata,
888                args.settings.keyring_provider,
889                &client_builder.subcommand(vec!["pip".to_owned(), "sync".to_owned()]),
890                args.settings.allow_empty_requirements,
891                globals.installer_metadata,
892                &args.settings.config_setting,
893                &args.settings.config_settings_package,
894                args.settings.build_isolation.clone(),
895                &args.settings.extra_build_dependencies,
896                &args.settings.extra_build_variables,
897                args.settings.build_options,
898                args.settings.python_version,
899                args.settings.python_platform,
900                globals.python_downloads,
901                args.settings.install_mirrors,
902                args.settings.strict,
903                args.settings.exclude_newer,
904                args.settings.python,
905                args.settings.system,
906                args.settings.break_system_packages,
907                args.settings.target,
908                args.settings.prefix,
909                args.settings.sources,
910                globals.python_preference,
911                globals.concurrency,
912                cache,
913                workspace_cache,
914                args.dry_run,
915                printer,
916                globals.preview,
917            ))
918            .await
919        }
920        Commands::Pip(PipNamespace {
921            command: PipCommand::Install(args),
922        }) => {
923            args.compat_args.validate()?;
924
925            // Resolve the settings from the command-line arguments and workspace configuration.
926            let mut args = PipInstallSettings::resolve(args, filesystem, environment);
927            show_settings!(args);
928
929            let mut requirements = Vec::with_capacity(
930                args.package.len() + args.editables.len() + args.requirements.len(),
931            );
932            for package in args.package {
933                requirements.push(RequirementsSource::from_package_argument(&package)?);
934            }
935            for package in args.editables {
936                requirements.push(RequirementsSource::from_editable(&package)?);
937            }
938            requirements.extend(
939                args.requirements
940                    .into_iter()
941                    .map(RequirementsSource::from_requirements_file)
942                    .collect::<Result<Vec<_>, _>>()?,
943            );
944            let constraints = args
945                .constraints
946                .into_iter()
947                .map(RequirementsSource::from_constraints_txt)
948                .collect::<Result<Vec<_>, _>>()?;
949            let overrides = args
950                .overrides
951                .into_iter()
952                .map(RequirementsSource::from_overrides_txt)
953                .collect::<Result<Vec<_>, _>>()?;
954            let excludes = args
955                .excludes
956                .into_iter()
957                .map(RequirementsSource::from_requirements_txt)
958                .collect::<Result<Vec<_>, _>>()?;
959            let build_constraints = args
960                .build_constraints
961                .into_iter()
962                .map(RequirementsSource::from_overrides_txt)
963                .collect::<Result<Vec<_>, _>>()?;
964            let groups = GroupsSpecification {
965                root: project_dir.to_path_buf(),
966                groups: args.settings.groups,
967            };
968
969            // Special-case: any source trees specified on the command-line are automatically
970            // reinstalled. This matches user expectations: `uv pip install .` should always
971            // re-build and re-install the package in the current working directory.
972            for requirement in &requirements {
973                let requirement = match requirement {
974                    RequirementsSource::Package(requirement) => requirement,
975                    RequirementsSource::Editable(requirement) => requirement,
976                    _ => continue,
977                };
978                match requirement {
979                    RequirementsTxtRequirement::Named(requirement) => {
980                        if let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() {
981                            if let ParsedUrl::Directory(ParsedDirectoryUrl {
982                                install_path, ..
983                            }) = &url.parsed_url
984                            {
985                                debug!(
986                                    "Marking explicit source tree for reinstall: `{}`",
987                                    install_path.display()
988                                );
989                                args.settings.reinstall = args
990                                    .settings
991                                    .reinstall
992                                    .with_package(requirement.name.clone());
993                            }
994                        }
995                    }
996                    RequirementsTxtRequirement::Unnamed(requirement) => {
997                        if let ParsedUrl::Directory(ParsedDirectoryUrl { install_path, .. }) =
998                            &requirement.url.parsed_url
999                        {
1000                            debug!(
1001                                "Marking explicit source tree for reinstall: `{}`",
1002                                install_path.display()
1003                            );
1004                            args.settings.reinstall =
1005                                args.settings.reinstall.with_path(install_path.clone());
1006                        }
1007                    }
1008                }
1009            }
1010
1011            // Check for conflicts between offline and refresh.
1012            globals
1013                .network_settings
1014                .check_refresh_conflict(&args.refresh);
1015
1016            // Initialize the cache.
1017            let cache = cache.init().await?.with_refresh(
1018                args.refresh
1019                    .combine(Refresh::from(args.settings.reinstall.clone()))
1020                    .combine(Refresh::from(args.settings.upgrade.clone())),
1021            );
1022
1023            Box::pin(commands::pip_install(
1024                &requirements,
1025                &constraints,
1026                &overrides,
1027                &excludes,
1028                &build_constraints,
1029                args.constraints_from_workspace,
1030                args.overrides_from_workspace,
1031                args.excludes_from_workspace,
1032                args.build_constraints_from_workspace,
1033                args.editable,
1034                &args.settings.extras,
1035                &groups,
1036                args.settings.resolution,
1037                args.settings.prerelease,
1038                args.settings.dependency_mode,
1039                args.settings.upgrade,
1040                args.settings.index_locations,
1041                args.settings.index_strategy,
1042                args.settings.torch_backend,
1043                args.settings.cuda_driver_version,
1044                args.settings.amd_gpu_architecture,
1045                args.settings.dependency_metadata,
1046                args.settings.keyring_provider,
1047                &client_builder.subcommand(vec!["pip".to_owned(), "install".to_owned()]),
1048                args.settings.reinstall,
1049                args.settings.link_mode,
1050                args.settings.compile_bytecode,
1051                args.settings.hash_checking,
1052                globals.installer_metadata,
1053                &args.settings.config_setting,
1054                &args.settings.config_settings_package,
1055                args.settings.build_isolation.clone(),
1056                &args.settings.extra_build_dependencies,
1057                &args.settings.extra_build_variables,
1058                args.settings.build_options,
1059                args.modifications,
1060                args.settings.python_version,
1061                args.settings.python_platform,
1062                globals.python_downloads,
1063                args.settings.install_mirrors,
1064                args.settings.strict,
1065                args.settings.exclude_newer,
1066                args.settings.sources,
1067                args.settings.python,
1068                args.settings.system,
1069                args.settings.break_system_packages,
1070                args.settings.target,
1071                args.settings.prefix,
1072                globals.python_preference,
1073                globals.concurrency,
1074                cache,
1075                workspace_cache,
1076                args.dry_run,
1077                printer,
1078                globals.preview,
1079            ))
1080            .await
1081        }
1082        Commands::Pip(PipNamespace {
1083            command: PipCommand::Uninstall(args),
1084        }) => {
1085            args.compat_args.validate()?;
1086
1087            // Resolve the settings from the command-line arguments and workspace configuration.
1088            let args = PipUninstallSettings::resolve(args, filesystem, environment);
1089            show_settings!(args);
1090
1091            // Initialize the cache.
1092            let cache = cache.init().await?;
1093
1094            let mut sources = Vec::with_capacity(args.package.len() + args.requirements.len());
1095            for package in args.package {
1096                sources.push(RequirementsSource::from_package_argument(&package)?);
1097            }
1098            sources.extend(
1099                args.requirements
1100                    .into_iter()
1101                    .map(RequirementsSource::from_requirements_file)
1102                    .collect::<Result<Vec<_>, _>>()?,
1103            );
1104            commands::pip_uninstall(
1105                &sources,
1106                args.settings.python,
1107                args.settings.system,
1108                args.settings.break_system_packages,
1109                args.settings.target,
1110                args.settings.prefix,
1111                cache,
1112                args.settings.keyring_provider,
1113                &client_builder.subcommand(vec!["pip".to_owned(), "uninstall".to_owned()]),
1114                args.dry_run,
1115                printer,
1116            )
1117            .await
1118        }
1119        Commands::Pip(PipNamespace {
1120            command: PipCommand::Freeze(args),
1121        }) => {
1122            // Resolve the settings from the command-line arguments and workspace configuration.
1123            let args = PipFreezeSettings::resolve(args, filesystem, environment);
1124            show_settings!(args);
1125
1126            // Initialize the cache.
1127            let cache = cache.init().await?;
1128
1129            commands::pip_freeze(
1130                args.exclude_editable,
1131                &args.exclude,
1132                args.settings.strict,
1133                &args.settings.dependency_metadata,
1134                args.settings.python.as_deref(),
1135                args.settings.system,
1136                args.settings.target,
1137                args.settings.prefix,
1138                args.paths,
1139                &cache,
1140                printer,
1141            )
1142        }
1143        Commands::Pip(PipNamespace {
1144            command: PipCommand::List(args),
1145        }) => {
1146            args.compat_args.validate()?;
1147
1148            // Resolve the settings from the command-line arguments and workspace configuration.
1149            let args = PipListSettings::resolve(args, filesystem, environment);
1150            show_settings!(args);
1151
1152            // Initialize the cache.
1153            let cache = cache.init().await?;
1154
1155            commands::pip_list(
1156                args.editable,
1157                &args.exclude,
1158                &args.format,
1159                args.outdated,
1160                args.settings.prerelease,
1161                args.settings.index_locations,
1162                args.settings.index_strategy,
1163                args.settings.keyring_provider,
1164                &client_builder.subcommand(vec!["pip".to_owned(), "list".to_owned()]),
1165                globals.concurrency,
1166                args.settings.strict,
1167                args.settings.exclude_newer,
1168                &args.settings.dependency_metadata,
1169                args.settings.python.as_deref(),
1170                args.settings.system,
1171                args.settings.target,
1172                args.settings.prefix,
1173                &cache,
1174                printer,
1175            )
1176            .await
1177        }
1178        Commands::Pip(PipNamespace {
1179            command: PipCommand::Show(args),
1180        }) => {
1181            // Resolve the settings from the command-line arguments and workspace configuration.
1182            let args = PipShowSettings::resolve(args, filesystem, environment);
1183            show_settings!(args);
1184
1185            // Initialize the cache.
1186            let cache = cache.init().await?;
1187
1188            commands::pip_show(
1189                args.package,
1190                args.settings.strict,
1191                &args.settings.dependency_metadata,
1192                args.settings.python.as_deref(),
1193                args.settings.system,
1194                args.settings.target,
1195                args.settings.prefix,
1196                args.files,
1197                &cache,
1198                printer,
1199            )
1200        }
1201        Commands::Pip(PipNamespace {
1202            command: PipCommand::Tree(args),
1203        }) => {
1204            // Resolve the settings from the command-line arguments and workspace configuration.
1205            let args = PipTreeSettings::resolve(args, filesystem, environment);
1206
1207            // Initialize the cache.
1208            let cache = cache.init().await?;
1209
1210            commands::pip_tree(
1211                args.show_version_specifiers,
1212                args.depth,
1213                &args.prune,
1214                &args.package,
1215                args.no_dedupe,
1216                args.invert,
1217                args.outdated,
1218                args.settings.prerelease,
1219                args.settings.index_locations,
1220                args.settings.index_strategy,
1221                args.settings.keyring_provider,
1222                client_builder.subcommand(vec!["pip".to_owned(), "tree".to_owned()]),
1223                globals.concurrency,
1224                args.settings.strict,
1225                args.settings.exclude_newer,
1226                &args.settings.dependency_metadata,
1227                args.settings.python.as_deref(),
1228                args.settings.system,
1229                &cache,
1230                printer,
1231            )
1232            .await
1233        }
1234        Commands::Pip(PipNamespace {
1235            command: PipCommand::Check(args),
1236        }) => {
1237            // Resolve the settings from the command-line arguments and workspace configuration.
1238            let args = PipCheckSettings::resolve(args, filesystem, environment);
1239            show_settings!(args);
1240
1241            // Initialize the cache.
1242            let cache = cache.init().await?;
1243
1244            commands::pip_check(
1245                args.settings.python.as_deref(),
1246                args.settings.system,
1247                args.settings.python_version.as_ref(),
1248                args.settings.python_platform.as_ref(),
1249                &args.settings.dependency_metadata,
1250                &cache,
1251                printer,
1252            )
1253        }
1254        Commands::Pip(PipNamespace {
1255            command: PipCommand::Debug(_),
1256        }) => Err(anyhow!(
1257            "pip's `debug` is unsupported (consider using `uvx pip debug` instead)"
1258        )),
1259        Commands::Cache(CacheNamespace {
1260            command: CacheCommand::Clean(args),
1261        })
1262        | Commands::Clean(args) => {
1263            show_settings!(args);
1264            commands::cache_clean(&args.package, args.force, cache, printer).await
1265        }
1266        Commands::Cache(CacheNamespace {
1267            command: CacheCommand::Prune(args),
1268        }) => {
1269            show_settings!(args);
1270            commands::cache_prune(args.ci, args.force, cache, printer).await
1271        }
1272        Commands::Cache(CacheNamespace {
1273            command: CacheCommand::Dir,
1274        }) => commands::cache_dir(&cache, printer),
1275        Commands::Cache(CacheNamespace {
1276            command: CacheCommand::Size(args),
1277        }) => commands::cache_size(&cache, args.human, printer, globals.preview),
1278        Commands::Build(args) => {
1279            // Resolve the settings from the command-line arguments and workspace configuration.
1280            let args = settings::BuildSettings::resolve(args, filesystem, environment);
1281            show_settings!(args);
1282
1283            // Check for conflicts between offline and refresh.
1284            globals
1285                .network_settings
1286                .check_refresh_conflict(&args.refresh);
1287
1288            // Initialize the cache.
1289            let cache = cache.init().await?.with_refresh(
1290                args.refresh
1291                    .combine(Refresh::from(args.settings.upgrade.clone())),
1292            );
1293
1294            // Resolve the build constraints.
1295            let build_constraints = args
1296                .build_constraints
1297                .into_iter()
1298                .map(RequirementsSource::from_constraints_txt)
1299                .collect::<Result<Vec<_>, _>>()?;
1300
1301            commands::build_frontend(
1302                &project_dir,
1303                args.src,
1304                args.package,
1305                args.all_packages,
1306                args.out_dir,
1307                args.sdist,
1308                args.wheel,
1309                args.list,
1310                args.build_logs,
1311                args.gitignore,
1312                args.force_pep517,
1313                args.clear,
1314                build_constraints,
1315                args.build_constraints_from_workspace,
1316                args.hash_checking,
1317                args.python,
1318                args.install_mirrors,
1319                &args.settings,
1320                &client_builder.subcommand(vec!["build".to_owned()]),
1321                cli.top_level.no_config,
1322                globals.python_preference,
1323                globals.python_downloads,
1324                globals.concurrency,
1325                &cache,
1326                &workspace_cache,
1327                printer,
1328                globals.preview,
1329            )
1330            .await
1331        }
1332        Commands::Venv(args) => {
1333            args.compat_args.validate()?;
1334
1335            if args.no_system {
1336                warn_user_once!(
1337                    "The `--no-system` flag has no effect, `uv venv` always ignores virtual environments when finding a Python interpreter; did you mean `--managed-python`?"
1338                );
1339            }
1340
1341            if args.system {
1342                warn_user_once!(
1343                    "The `--system` flag has no effect, `uv venv` always ignores virtual environments when finding a Python interpreter; did you mean `--no-managed-python`?"
1344                );
1345            }
1346
1347            // Resolve the settings from the command-line arguments and workspace configuration.
1348            let args = settings::VenvSettings::resolve(args, filesystem, environment);
1349            show_settings!(args);
1350
1351            // Check for conflicts between offline and refresh.
1352            globals
1353                .network_settings
1354                .check_refresh_conflict(&args.refresh);
1355
1356            // Initialize the cache.
1357            let cache = cache.init().await?.with_refresh(
1358                args.refresh
1359                    .combine(Refresh::from(args.settings.reinstall.clone()))
1360                    .combine(Refresh::from(args.settings.upgrade.clone())),
1361            );
1362
1363            // Since we use ".venv" as the default name, we use "." as the default prompt.
1364            let prompt = args.prompt.or_else(|| {
1365                if args.path.is_none() {
1366                    Some(".".to_string())
1367                } else {
1368                    None
1369                }
1370            });
1371
1372            let python_request: Option<PythonRequest> =
1373                args.settings.python.as_deref().map(PythonRequest::parse);
1374
1375            let on_existing = uv_virtualenv::OnExisting::from_args(
1376                args.allow_existing,
1377                args.clear,
1378                args.no_clear,
1379                if args.force {
1380                    uv_virtualenv::ClearNonVirtualenv::Allow
1381                } else if globals.preview.is_enabled(PreviewFeature::VenvSafeClear) {
1382                    uv_virtualenv::ClearNonVirtualenv::Error
1383                } else {
1384                    uv_virtualenv::ClearNonVirtualenv::Warn
1385                },
1386            );
1387
1388            Box::pin(commands::venv(
1389                &project_dir,
1390                args.path,
1391                python_request,
1392                args.settings.install_mirrors,
1393                globals.python_preference,
1394                globals.python_downloads,
1395                args.settings.link_mode,
1396                &args.settings.index_locations,
1397                args.settings.index_strategy,
1398                args.settings.dependency_metadata,
1399                args.settings.keyring_provider,
1400                &client_builder.subcommand(vec!["venv".to_owned()]),
1401                uv_virtualenv::Prompt::from_args(prompt),
1402                args.system_site_packages,
1403                args.seed,
1404                on_existing,
1405                args.settings.exclude_newer,
1406                globals.concurrency,
1407                cli.top_level.no_config,
1408                args.no_project,
1409                &cache,
1410                &workspace_cache,
1411                printer,
1412                args.relocatable
1413                    || (globals
1414                        .preview
1415                        .is_enabled(PreviewFeature::RelocatableEnvsDefault)
1416                        && !args.no_relocatable),
1417                globals.preview,
1418            ))
1419            .await
1420        }
1421        Commands::Project(project) => {
1422            Box::pin(run_project(
1423                project,
1424                &project_dir,
1425                run_command,
1426                script,
1427                globals,
1428                cli.top_level.no_config,
1429                cli.top_level.global_args.project.is_some(),
1430                client_builder,
1431                filesystem,
1432                cache,
1433                &workspace_cache,
1434                printer,
1435            ))
1436            .await
1437        }
1438        #[cfg(feature = "self-update")]
1439        Commands::Self_(SelfNamespace {
1440            command:
1441                SelfCommand::Update(SelfUpdateArgs {
1442                    target_version,
1443                    token,
1444                    dry_run,
1445                }),
1446        }) => {
1447            commands::self_update(
1448                target_version,
1449                token,
1450                dry_run,
1451                printer,
1452                client_builder.subcommand(vec!["self".to_owned(), "update".to_owned()]),
1453            )
1454            .await
1455        }
1456        Commands::Self_(SelfNamespace {
1457            command:
1458                SelfCommand::Version {
1459                    short,
1460                    output_format,
1461                },
1462        }) => {
1463            commands::self_version(short, output_format, printer)?;
1464            Ok(ExitStatus::Success)
1465        }
1466        #[cfg(not(feature = "self-update"))]
1467        Commands::Self_(_) => {
1468            return Err(ExternallyInstalledError {
1469                install_source: InstallSource::detect(),
1470            }
1471            .into());
1472        }
1473        Commands::GenerateShellCompletion(args) => {
1474            args.shell.generate(&mut Cli::command(), &mut stdout());
1475            Ok(ExitStatus::Success)
1476        }
1477        Commands::Tool(ToolNamespace {
1478            command: run_variant @ (ToolCommand::Uvx(_) | ToolCommand::Run(_)),
1479        }) => {
1480            let (args, invocation_source) = match run_variant {
1481                ToolCommand::Uvx(args) => (args.tool_run, ToolRunCommand::Uvx),
1482                ToolCommand::Run(args) => (args, ToolRunCommand::ToolRun),
1483                // OK guarded by the outer match statement
1484                _ => unreachable!(),
1485            };
1486
1487            if let Some(shell) = args.generate_shell_completion {
1488                // uvx: combine `uv tool uvx` with the top-level arguments
1489                let mut uvx = Cli::command()
1490                    .find_subcommand("tool")
1491                    .unwrap()
1492                    .find_subcommand("uvx")
1493                    .unwrap()
1494                    .clone()
1495                    // Avoid duplicating the `--help` and `--version` flags from the top-level
1496                    // arguments.
1497                    .disable_help_flag(true)
1498                    .disable_version_flag(true);
1499
1500                // Copy the top-level arguments into the `uvx` command, as in `Args::augment_args`,
1501                // but expanded to skip collisions.
1502                for arg in TopLevelArgs::command().get_arguments() {
1503                    if arg.get_id() != "isolated" && arg.get_id() != "version" {
1504                        uvx = uvx.arg(arg);
1505                    }
1506                }
1507                shell.generate(&mut uvx, &mut stdout());
1508                return Ok(ExitStatus::Success);
1509            }
1510
1511            // Resolve the settings from the command-line arguments and workspace configuration.
1512            let args = settings::ToolRunSettings::resolve(
1513                args,
1514                filesystem,
1515                invocation_source,
1516                environment,
1517            );
1518            show_settings!(args);
1519
1520            // Check for conflicts between offline and refresh.
1521            globals
1522                .network_settings
1523                .check_refresh_conflict(&args.refresh);
1524
1525            // Initialize the cache.
1526            let cache = cache.init().await?.with_refresh(
1527                args.refresh
1528                    .combine(Refresh::from(args.settings.reinstall.clone()))
1529                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
1530            );
1531
1532            let requirements = {
1533                let mut requirements = Vec::with_capacity(
1534                    args.with.len() + args.with_editable.len() + args.with_requirements.len(),
1535                );
1536                for package in args.with {
1537                    requirements.push(RequirementsSource::from_with_package_argument(&package)?);
1538                }
1539                for package in args.with_editable {
1540                    requirements.push(RequirementsSource::from_editable(&package)?);
1541                }
1542                requirements.extend(
1543                    args.with_requirements
1544                        .into_iter()
1545                        .map(RequirementsSource::from_requirements_file)
1546                        .collect::<Result<Vec<_>, _>>()?,
1547                );
1548                requirements
1549            };
1550            let constraints = args
1551                .constraints
1552                .into_iter()
1553                .map(RequirementsSource::from_constraints_txt)
1554                .collect::<Result<Vec<_>, _>>()?;
1555            let overrides = args
1556                .overrides
1557                .into_iter()
1558                .map(RequirementsSource::from_overrides_txt)
1559                .collect::<Result<Vec<_>, _>>()?;
1560
1561            let build_constraints = args
1562                .build_constraints
1563                .into_iter()
1564                .map(RequirementsSource::from_constraints_txt)
1565                .collect::<Result<Vec<_>, _>>()?;
1566
1567            let client_builder = match invocation_source {
1568                ToolRunCommand::Uvx => client_builder.subcommand(vec!["uvx".to_owned()]),
1569                ToolRunCommand::ToolRun => {
1570                    client_builder.subcommand(vec!["tool".to_owned(), "run".to_owned()])
1571                }
1572            };
1573
1574            Box::pin(commands::tool_run(
1575                args.command,
1576                args.from,
1577                &requirements,
1578                &constraints,
1579                &overrides,
1580                &build_constraints,
1581                args.show_resolution || globals.verbose > 0,
1582                args.lfs,
1583                args.python,
1584                args.python_platform,
1585                args.install_mirrors,
1586                args.options,
1587                args.settings,
1588                client_builder,
1589                invocation_source,
1590                args.isolated,
1591                globals.python_preference,
1592                globals.python_downloads,
1593                globals.installer_metadata,
1594                globals.concurrency,
1595                cache,
1596                workspace_cache,
1597                printer,
1598                args.env_file,
1599                args.no_env_file,
1600                globals.preview,
1601            ))
1602            .await
1603        }
1604        Commands::Tool(ToolNamespace {
1605            command: ToolCommand::Install(args),
1606        }) => {
1607            // Resolve the settings from the command-line arguments and workspace configuration.
1608            let args = settings::ToolInstallSettings::resolve(args, filesystem, environment);
1609            show_settings!(args);
1610
1611            // Check for conflicts between offline and refresh.
1612            globals
1613                .network_settings
1614                .check_refresh_conflict(&args.refresh);
1615
1616            // Initialize the cache.
1617            let refresh = args
1618                .refresh
1619                .combine(Refresh::from(args.settings.reinstall.clone()))
1620                .combine(Refresh::from(args.settings.resolver.upgrade.clone()));
1621            let cache = cache.init().await?.with_refresh(refresh.clone());
1622
1623            let mut entrypoints = Vec::with_capacity(args.with_executables_from.len());
1624            let mut requirements = Vec::with_capacity(
1625                args.with.len()
1626                    + args.with_editable.len()
1627                    + args.with_requirements.len()
1628                    + args.with_executables_from.len(),
1629            );
1630            for pkg in args.with {
1631                requirements.push(RequirementsSource::from_with_package_argument(&pkg)?);
1632            }
1633            for pkg in args.with_editable {
1634                requirements.push(RequirementsSource::from_editable(&pkg)?);
1635            }
1636            for path in args.with_requirements {
1637                requirements.push(RequirementsSource::from_requirements_file(path)?);
1638            }
1639            for pkg in &args.with_executables_from {
1640                let source = RequirementsSource::from_with_package_argument(pkg)?;
1641                let RequirementsSource::Package(RequirementsTxtRequirement::Named(requirement)) =
1642                    &source
1643                else {
1644                    bail!(
1645                        "Expected a named package for `--with-executables-from`, but got: {}",
1646                        source.to_string().cyan()
1647                    )
1648                };
1649                entrypoints.push(requirement.name.clone());
1650                requirements.push(source);
1651            }
1652
1653            let constraints = args
1654                .constraints
1655                .into_iter()
1656                .map(RequirementsSource::from_constraints_txt)
1657                .collect::<Result<Vec<_>, _>>()?;
1658            let overrides = args
1659                .overrides
1660                .into_iter()
1661                .map(RequirementsSource::from_overrides_txt)
1662                .collect::<Result<Vec<_>, _>>()?;
1663            let excludes = args
1664                .excludes
1665                .into_iter()
1666                .map(RequirementsSource::from_requirements_txt)
1667                .collect::<Result<Vec<_>, _>>()?;
1668            let build_constraints = args
1669                .build_constraints
1670                .into_iter()
1671                .map(RequirementsSource::from_constraints_txt)
1672                .collect::<Result<Vec<_>, _>>()?;
1673
1674            Box::pin(commands::tool_install(
1675                args.package,
1676                args.editable,
1677                args.from,
1678                &requirements,
1679                &constraints,
1680                &overrides,
1681                &excludes,
1682                &build_constraints,
1683                &entrypoints,
1684                args.lfs,
1685                args.python,
1686                args.python_platform,
1687                args.install_mirrors,
1688                args.force,
1689                args.options,
1690                args.settings,
1691                client_builder.subcommand(vec!["tool".to_owned(), "install".to_owned()]),
1692                globals.python_preference,
1693                globals.python_downloads,
1694                globals.installer_metadata,
1695                globals.concurrency,
1696                cli.top_level.no_config,
1697                cache,
1698                refresh,
1699                &workspace_cache,
1700                printer,
1701                globals.preview,
1702            ))
1703            .await
1704        }
1705        Commands::Tool(ToolNamespace {
1706            command: ToolCommand::List(args),
1707        }) => {
1708            // Resolve the settings from the command-line arguments and workspace configuration.
1709            let args = settings::ToolListSettings::resolve(args, filesystem);
1710            show_settings!(args);
1711
1712            // Initialize the cache.
1713            let cache = cache.init().await?;
1714
1715            commands::tool_list(
1716                args.show_paths,
1717                args.show_version_specifiers,
1718                args.show_with,
1719                args.show_extras,
1720                args.show_python,
1721                args.outdated,
1722                args.args,
1723                args.filesystem,
1724                client_builder.subcommand(vec!["tool".to_owned(), "list".to_owned()]),
1725                globals.concurrency,
1726                &cache,
1727                printer,
1728            )
1729            .await
1730        }
1731        Commands::Tool(ToolNamespace {
1732            command: ToolCommand::Upgrade(args),
1733        }) => {
1734            // Resolve the settings from the command-line arguments and workspace configuration.
1735            let args = settings::ToolUpgradeSettings::resolve(args, filesystem, &environment);
1736            show_settings!(args);
1737
1738            // Initialize the cache.
1739            let cache = cache
1740                .init()
1741                .await?
1742                .with_refresh(Refresh::All(Timestamp::now()));
1743
1744            Box::pin(commands::tool_upgrade(
1745                args.names,
1746                args.python,
1747                args.python_platform,
1748                args.install_mirrors,
1749                args.args,
1750                args.filesystem,
1751                client_builder.subcommand(vec!["tool".to_owned(), "upgrade".to_owned()]),
1752                globals.python_preference,
1753                globals.python_downloads,
1754                globals.installer_metadata,
1755                globals.concurrency,
1756                &cache,
1757                &workspace_cache,
1758                printer,
1759                globals.preview,
1760            ))
1761            .await
1762        }
1763        Commands::Tool(ToolNamespace {
1764            command: ToolCommand::Uninstall(args),
1765        }) => {
1766            // Resolve the settings from the command-line arguments and workspace configuration.
1767            let args = settings::ToolUninstallSettings::resolve(args, filesystem);
1768            show_settings!(args);
1769
1770            commands::tool_uninstall(args.name, printer).await
1771        }
1772        Commands::Tool(ToolNamespace {
1773            command: ToolCommand::UpdateShell,
1774        }) => {
1775            commands::tool_update_shell(printer).await?;
1776            Ok(ExitStatus::Success)
1777        }
1778        Commands::Tool(ToolNamespace {
1779            command: ToolCommand::Dir(args),
1780        }) => {
1781            // Resolve the settings from the command-line arguments and workspace configuration.
1782            let args = settings::ToolDirSettings::resolve(args, filesystem);
1783            show_settings!(args);
1784
1785            commands::tool_dir(args.bin, globals.preview, printer)?;
1786            Ok(ExitStatus::Success)
1787        }
1788        Commands::Python(PythonNamespace {
1789            command: PythonCommand::List(args),
1790        }) => {
1791            // Resolve the settings from the command-line arguments and workspace configuration.
1792            let args = settings::PythonListSettings::resolve(args, filesystem, environment);
1793            show_settings!(args);
1794
1795            // Initialize the cache.
1796            let cache = cache.init().await?;
1797
1798            commands::python_list(
1799                args.request,
1800                args.kinds,
1801                args.all_versions,
1802                args.all_platforms,
1803                args.all_arches,
1804                args.show_urls,
1805                args.output_format,
1806                args.python_downloads_json_url,
1807                args.python_install_mirror,
1808                args.pypy_install_mirror,
1809                globals.python_preference,
1810                globals.python_downloads,
1811                &client_builder.subcommand(vec!["python".to_owned(), "list".to_owned()]),
1812                &cache,
1813                printer,
1814            )
1815            .await
1816        }
1817        Commands::Python(PythonNamespace {
1818            command: PythonCommand::Install(args),
1819        }) => {
1820            // Resolve the settings from the command-line arguments and workspace configuration.
1821            let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
1822            show_settings!(args);
1823
1824            // Initialize the cache.
1825            let cache = cache.init().await?;
1826
1827            commands::python_install(
1828                &project_dir,
1829                args.install_dir,
1830                args.targets,
1831                args.reinstall,
1832                args.upgrade,
1833                args.bin,
1834                args.registry,
1835                args.force,
1836                args.python_install_mirror,
1837                args.pypy_install_mirror,
1838                args.python_downloads_json_url,
1839                client_builder.subcommand(vec!["python".to_owned(), "install".to_owned()]),
1840                args.default,
1841                globals.python_downloads,
1842                cli.top_level.no_config,
1843                args.compile_bytecode,
1844                &globals.concurrency,
1845                &cache,
1846                globals.preview,
1847                printer,
1848            )
1849            .await
1850        }
1851        Commands::Python(PythonNamespace {
1852            command: PythonCommand::Upgrade(args),
1853        }) => {
1854            // Resolve the settings from the command-line arguments and workspace configuration.
1855            let args = settings::PythonUpgradeSettings::resolve(args, filesystem, environment);
1856            show_settings!(args);
1857            let upgrade = commands::PythonUpgrade::Enabled(commands::PythonUpgradeSource::Upgrade);
1858
1859            // Initialize the cache.
1860            let cache = cache.init().await?;
1861
1862            commands::python_install(
1863                &project_dir,
1864                args.install_dir,
1865                args.targets,
1866                args.reinstall,
1867                upgrade,
1868                args.bin,
1869                args.registry,
1870                args.force,
1871                args.python_install_mirror,
1872                args.pypy_install_mirror,
1873                args.python_downloads_json_url,
1874                client_builder.subcommand(vec!["python".to_owned(), "upgrade".to_owned()]),
1875                args.default,
1876                globals.python_downloads,
1877                cli.top_level.no_config,
1878                args.compile_bytecode,
1879                &globals.concurrency,
1880                &cache,
1881                globals.preview,
1882                printer,
1883            )
1884            .await
1885        }
1886        Commands::Python(PythonNamespace {
1887            command: PythonCommand::Uninstall(args),
1888        }) => {
1889            // Resolve the settings from the command-line arguments and workspace configuration.
1890            let args = settings::PythonUninstallSettings::resolve(args, filesystem);
1891            show_settings!(args);
1892
1893            commands::python_uninstall(args.install_dir, args.targets, args.all, printer).await
1894        }
1895        Commands::Python(PythonNamespace {
1896            command: PythonCommand::Find(args),
1897        }) => {
1898            // Resolve the settings from the command-line arguments and workspace configuration.
1899            let args = settings::PythonFindSettings::resolve(args, filesystem, environment);
1900
1901            // Initialize the cache.
1902            let cache = cache.init().await?;
1903
1904            if let Some(Pep723Item::Script(script)) = script {
1905                commands::python_find_script(
1906                    (&script).into(),
1907                    args.show_version,
1908                    args.resolve_links,
1909                    // TODO(zsol): is this the right thing to do here?
1910                    &client_builder.subcommand(vec!["python".to_owned(), "find".to_owned()]),
1911                    globals.python_preference,
1912                    globals.python_downloads,
1913                    cli.top_level.no_config,
1914                    &cache,
1915                    printer,
1916                )
1917                .await
1918            } else {
1919                commands::python_find(
1920                    &project_dir,
1921                    args.request,
1922                    args.show_version,
1923                    args.resolve_links,
1924                    args.no_project,
1925                    cli.top_level.no_config,
1926                    args.system,
1927                    globals.python_preference,
1928                    args.python_downloads_json_url.as_deref(),
1929                    &client_builder.subcommand(vec!["python".to_owned(), "find".to_owned()]),
1930                    &cache,
1931                    &workspace_cache,
1932                    printer,
1933                )
1934                .await
1935            }
1936        }
1937        Commands::Python(PythonNamespace {
1938            command: PythonCommand::Pin(args),
1939        }) => {
1940            // Resolve the settings from the command-line arguments and workspace configuration.
1941            let args = settings::PythonPinSettings::resolve(args, filesystem, environment);
1942
1943            // Initialize the cache.
1944            let cache = cache.init().await?;
1945
1946            Box::pin(commands::python_pin(
1947                &project_dir,
1948                args.request,
1949                args.resolved,
1950                globals.python_preference,
1951                globals.python_downloads,
1952                args.no_project,
1953                args.global,
1954                args.rm,
1955                args.install_mirrors,
1956                client_builder.subcommand(vec!["python".to_owned(), "pin".to_owned()]),
1957                &cache,
1958                &workspace_cache,
1959                printer,
1960            ))
1961            .await
1962        }
1963        Commands::Python(PythonNamespace {
1964            command: PythonCommand::Dir(args),
1965        }) => {
1966            // Resolve the settings from the command-line arguments and workspace configuration.
1967            let args = settings::PythonDirSettings::resolve(args, filesystem);
1968            show_settings!(args);
1969
1970            commands::python_dir(args.bin, printer)?;
1971            Ok(ExitStatus::Success)
1972        }
1973        Commands::Python(PythonNamespace {
1974            command: PythonCommand::UpdateShell,
1975        }) => {
1976            commands::python_update_shell(printer).await?;
1977            Ok(ExitStatus::Success)
1978        }
1979        Commands::Publish(args) => {
1980            show_settings!(args);
1981
1982            if args.skip_existing {
1983                bail!(
1984                    "`uv publish` does not support `--skip-existing` because there is not a \
1985                    reliable way to identify when an upload fails due to an existing \
1986                    distribution. Instead, use `--check-url` to provide the URL to the simple \
1987                    API for your index. uv will check the index for existing distributions before \
1988                    attempting uploads."
1989                );
1990            }
1991
1992            // Resolve the settings from the command-line arguments and workspace configuration.
1993            let PublishSettings {
1994                files,
1995                username,
1996                password,
1997                dry_run,
1998                no_attestations,
1999                direct,
2000                publish_url,
2001                trusted_publishing,
2002                keyring_provider,
2003                check_url,
2004                index,
2005                index_locations,
2006            } = PublishSettings::resolve(args, filesystem);
2007
2008            commands::publish(
2009                files,
2010                publish_url,
2011                trusted_publishing,
2012                keyring_provider,
2013                &environment,
2014                &client_builder.subcommand(vec!["publish".to_owned()]),
2015                username,
2016                password,
2017                check_url,
2018                index,
2019                index_locations,
2020                dry_run,
2021                no_attestations,
2022                direct,
2023                globals.preview,
2024                &cache,
2025                printer,
2026            )
2027            .await
2028        }
2029        Commands::Workspace(WorkspaceNamespace { command }) => match command {
2030            WorkspaceCommand::Metadata(args) => {
2031                // Resolve the settings from the command-line arguments and workspace configuration.
2032                let args = settings::MetadataSettings::resolve(args, filesystem, environment);
2033                show_settings!(args);
2034
2035                // Check for conflicts between offline and refresh.
2036                globals
2037                    .network_settings
2038                    .check_refresh_conflict(&args.refresh);
2039
2040                // Initialize the cache.
2041                let cache = cache.init().await?.with_refresh(
2042                    args.refresh
2043                        .clone()
2044                        .combine(Refresh::from(args.settings.upgrade.clone())),
2045                );
2046
2047                let script = script.and_then(|script| match script {
2048                    Pep723Item::Script(script) => Some(script),
2049                    Pep723Item::Remote(..) | Pep723Item::Stdin(..) => None,
2050                });
2051
2052                Box::pin(commands::metadata(
2053                    &project_dir,
2054                    args.lock_check,
2055                    args.frozen,
2056                    args.dry_run,
2057                    args.refresh,
2058                    args.sync,
2059                    args.python,
2060                    args.install_mirrors,
2061                    args.malware_settings,
2062                    args.settings,
2063                    client_builder.subcommand(vec!["workspace".to_owned(), "metadata".to_owned()]),
2064                    script,
2065                    globals.python_preference,
2066                    globals.python_downloads,
2067                    globals.concurrency,
2068                    cli.top_level.no_config,
2069                    &cache,
2070                    &workspace_cache,
2071                    printer,
2072                    globals.preview,
2073                ))
2074                .await
2075            }
2076            WorkspaceCommand::Dir(args) => {
2077                commands::dir(
2078                    args.package,
2079                    &project_dir,
2080                    &cache,
2081                    &workspace_cache,
2082                    printer,
2083                )
2084                .await
2085            }
2086            WorkspaceCommand::List(args) => {
2087                commands::list(
2088                    &project_dir,
2089                    args.paths,
2090                    args.scripts,
2091                    &cache,
2092                    &workspace_cache,
2093                    printer,
2094                    globals.preview,
2095                )
2096                .await
2097            }
2098        },
2099        Commands::BuildBackend { command } => spawn_blocking(move || match command {
2100            BuildBackendCommand::BuildSdist { sdist_directory } => {
2101                commands::build_backend::build_sdist(&sdist_directory)
2102            }
2103            BuildBackendCommand::BuildWheel {
2104                wheel_directory,
2105                metadata_directory,
2106            } => commands::build_backend::build_wheel(
2107                &wheel_directory,
2108                metadata_directory.as_deref(),
2109            ),
2110            BuildBackendCommand::BuildEditable {
2111                wheel_directory,
2112                metadata_directory,
2113            } => commands::build_backend::build_editable(
2114                &wheel_directory,
2115                metadata_directory.as_deref(),
2116            ),
2117            BuildBackendCommand::GetRequiresForBuildSdist => {
2118                commands::build_backend::get_requires_for_build_sdist()
2119            }
2120            BuildBackendCommand::GetRequiresForBuildWheel => {
2121                commands::build_backend::get_requires_for_build_wheel()
2122            }
2123            BuildBackendCommand::PrepareMetadataForBuildWheel { wheel_directory } => {
2124                commands::build_backend::prepare_metadata_for_build_wheel(&wheel_directory)
2125            }
2126            BuildBackendCommand::GetRequiresForBuildEditable => {
2127                commands::build_backend::get_requires_for_build_editable()
2128            }
2129            BuildBackendCommand::PrepareMetadataForBuildEditable { wheel_directory } => {
2130                commands::build_backend::prepare_metadata_for_build_editable(&wheel_directory)
2131            }
2132        })
2133        .await
2134        .expect("tokio threadpool exited unexpectedly"),
2135    }
2136}
2137
2138fn map_settings_error(err: uv_settings::Error) -> anyhow::Error {
2139    match err {
2140        uv_settings::Error::RequiredVersion {
2141            required_version,
2142            package_version,
2143        } => required_version_error(&required_version, &package_version),
2144        err => err.into(),
2145    }
2146}
2147
2148fn required_version_error(
2149    required_version: &uv_configuration::RequiredVersion,
2150    package_version: &uv_pep440::Version,
2151) -> anyhow::Error {
2152    #[cfg(feature = "self-update")]
2153    let hint = {
2154        // If the required version range includes a lower bound that's higher than the current
2155        // version, suggest `uv self update`.
2156        let ranges = release_specifiers_to_ranges(required_version.specifiers().clone());
2157
2158        if let Some(singleton) = ranges.as_singleton() {
2159            format!(
2160                ". Update `uv` by running `{}`.",
2161                format!("uv self update {singleton}").green()
2162            )
2163        } else if ranges
2164            .bounding_range()
2165            .iter()
2166            .any(|(lowest, _highest)| match lowest {
2167                Bound::Included(version) => **version > *package_version,
2168                Bound::Excluded(version) => **version > *package_version,
2169                Bound::Unbounded => false,
2170            })
2171        {
2172            format!(". Update `uv` by running `{}`.", "uv self update".cyan())
2173        } else {
2174            String::new()
2175        }
2176    };
2177    #[cfg(not(feature = "self-update"))]
2178    let hint = "";
2179
2180    anyhow!(
2181        "Required uv version `{required_version}` does not match the running version `{package_version}`{hint}",
2182    )
2183}
2184
2185/// Run a [`ProjectCommand`].
2186async fn run_project(
2187    project_command: Box<ProjectCommand>,
2188    project_dir: &Path,
2189    command: Option<RunCommand>,
2190    script: Option<Pep723Item>,
2191    globals: GlobalSettings,
2192    // TODO(zanieb): Determine a better story for passing `no_config` in here
2193    no_config: bool,
2194    explicit_project: bool,
2195    client_builder: BaseClientBuilder<'_>,
2196    filesystem: Option<FilesystemOptions>,
2197    cache: Cache,
2198    workspace_cache: &WorkspaceCache,
2199    printer: Printer,
2200) -> Result<ExitStatus> {
2201    // Write out any resolved settings.
2202    macro_rules! show_settings {
2203        ($arg:expr) => {
2204            if globals.show_settings {
2205                writeln!(printer.stdout(), "{:#?}", $arg)?;
2206                return Ok(ExitStatus::Success);
2207            }
2208        };
2209    }
2210
2211    // Load environment variables not handled by Clap
2212    let environment = EnvironmentOptions::new()?;
2213
2214    match *project_command {
2215        ProjectCommand::Init(args) => {
2216            // Resolve the settings from the command-line arguments and workspace configuration.
2217            let args =
2218                settings::InitSettings::resolve(args, filesystem, environment, globals.preview)?;
2219            show_settings!(args);
2220
2221            // The `--project` arg is being deprecated for `init` with a warning now and an error in preview.
2222            if explicit_project {
2223                if globals.preview.is_enabled(PreviewFeature::InitProjectFlag) {
2224                    bail!(
2225                        "The `--project` option cannot be used in `uv init`. {}",
2226                        if args.path.is_some() {
2227                            "Use `--directory` instead."
2228                        } else {
2229                            "Use `--directory` or a positional path instead."
2230                        }
2231                    )
2232                }
2233
2234                warn_user!(
2235                    "Use of the `--project` option in `uv init` is deprecated and will be removed in a future release. {}",
2236                    if args.path.is_some() {
2237                        "Since a positional path was provided, the `--project` option has no effect. Consider using `--directory` instead."
2238                    } else {
2239                        "Consider using `uv init <PATH>` instead."
2240                    }
2241                );
2242            }
2243
2244            // Initialize the cache.
2245            let cache = cache.init().await?;
2246
2247            Box::pin(commands::init(
2248                project_dir,
2249                args.path,
2250                args.name,
2251                args.package,
2252                args.kind,
2253                args.bare,
2254                args.description,
2255                args.no_description,
2256                args.vcs,
2257                args.build_backend,
2258                args.no_readme,
2259                args.author_from,
2260                args.pin_python,
2261                args.python,
2262                args.install_mirrors,
2263                args.no_workspace,
2264                &client_builder.subcommand(vec!["init".to_owned()]),
2265                globals.python_preference,
2266                globals.python_downloads,
2267                no_config,
2268                &cache,
2269                printer,
2270            ))
2271            .await
2272        }
2273        ProjectCommand::Run(args) => {
2274            // Resolve the settings from the command-line arguments and workspace configuration.
2275            let args = settings::RunSettings::resolve(args, filesystem, environment);
2276            show_settings!(args);
2277
2278            // Check for conflicts between offline and refresh.
2279            globals
2280                .network_settings
2281                .check_refresh_conflict(&args.refresh);
2282
2283            // Initialize the cache.
2284            let cache = cache.init().await?.with_refresh(
2285                args.refresh
2286                    .combine(Refresh::from(args.settings.reinstall.clone()))
2287                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2288            );
2289
2290            let mut requirements = Vec::with_capacity(
2291                args.with.len() + args.with_editable.len() + args.with_requirements.len(),
2292            );
2293            for package in args.with {
2294                requirements.push(RequirementsSource::from_with_package_argument(&package)?);
2295            }
2296            for package in args.with_editable {
2297                requirements.push(RequirementsSource::from_editable(&package)?);
2298            }
2299            requirements.extend(
2300                args.with_requirements
2301                    .into_iter()
2302                    .map(RequirementsSource::from_requirements_file)
2303                    .collect::<Result<Vec<_>, _>>()?,
2304            );
2305
2306            Box::pin(commands::run(
2307                project_dir,
2308                script,
2309                command,
2310                requirements,
2311                args.show_resolution || globals.verbose > 0,
2312                args.lock_check,
2313                args.frozen,
2314                args.active,
2315                args.no_sync,
2316                args.isolated,
2317                args.all_packages,
2318                args.package,
2319                args.no_project,
2320                no_config,
2321                args.extras,
2322                args.groups,
2323                args.editable,
2324                args.modifications,
2325                args.python,
2326                args.python_platform,
2327                args.install_mirrors,
2328                args.settings,
2329                client_builder.subcommand(vec!["run".to_owned()]),
2330                globals.python_preference,
2331                globals.python_downloads,
2332                globals.installer_metadata,
2333                globals.concurrency,
2334                cache,
2335                workspace_cache,
2336                printer,
2337                args.env_file,
2338                globals.preview,
2339                args.max_recursion_depth,
2340                args.malware_settings,
2341            ))
2342            .await
2343        }
2344        ProjectCommand::Sync(args) => {
2345            // Resolve the settings from the command-line arguments and workspace configuration.
2346            let args = settings::SyncSettings::resolve(args, filesystem, environment);
2347            show_settings!(args);
2348
2349            // Check for conflicts between offline and refresh.
2350            globals
2351                .network_settings
2352                .check_refresh_conflict(&args.refresh);
2353
2354            // Initialize the cache.
2355            let cache = cache.init().await?.with_refresh(
2356                args.refresh
2357                    .combine(Refresh::from(args.settings.reinstall.clone()))
2358                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2359            );
2360
2361            // Unwrap the script.
2362            let script = script.map(|script| match script {
2363                Pep723Item::Script(script) => script,
2364                Pep723Item::Stdin(..) => unreachable!("`uv lock` does not support stdin"),
2365                Pep723Item::Remote(..) => unreachable!("`uv lock` does not support remote files"),
2366            });
2367
2368            Box::pin(commands::sync(
2369                project_dir,
2370                args.lock_check,
2371                args.frozen,
2372                args.dry_run,
2373                args.active,
2374                args.all_packages,
2375                args.package,
2376                args.extras,
2377                args.groups,
2378                args.editable,
2379                args.install_options,
2380                args.modifications,
2381                args.python,
2382                args.python_platform,
2383                args.install_mirrors,
2384                globals.python_preference,
2385                globals.python_downloads,
2386                args.settings,
2387                client_builder.subcommand(vec!["sync".to_owned()]),
2388                script,
2389                globals.installer_metadata,
2390                globals.concurrency,
2391                no_config,
2392                &cache,
2393                workspace_cache,
2394                printer,
2395                globals.preview,
2396                args.output_format,
2397                args.malware_settings,
2398            ))
2399            .await
2400        }
2401        ProjectCommand::Lock(args) => {
2402            // Resolve the settings from the command-line arguments and workspace configuration.
2403            let args = settings::LockSettings::resolve(args, filesystem, environment);
2404            show_settings!(args);
2405
2406            // Check for conflicts between offline and refresh.
2407            globals
2408                .network_settings
2409                .check_refresh_conflict(&args.refresh);
2410
2411            // Initialize the cache.
2412            let cache = cache.init().await?.with_refresh(
2413                args.refresh
2414                    .clone()
2415                    .combine(Refresh::from(args.settings.upgrade.clone())),
2416            );
2417
2418            // If the script already exists, use it; otherwise, propagate the file path and we'll
2419            // initialize it later on.
2420            let script = script
2421                .map(|script| match script {
2422                    Pep723Item::Script(script) => script,
2423                    Pep723Item::Stdin(..) => unreachable!("`uv add` does not support stdin"),
2424                    Pep723Item::Remote(..) => {
2425                        unreachable!("`uv add` does not support remote files")
2426                    }
2427                })
2428                .map(ScriptPath::Script)
2429                .or(args.script.map(ScriptPath::Path));
2430
2431            Box::pin(commands::lock(
2432                project_dir,
2433                args.lock_check,
2434                args.frozen,
2435                args.dry_run,
2436                args.refresh,
2437                args.python,
2438                args.install_mirrors,
2439                args.settings,
2440                client_builder.subcommand(vec!["lock".to_owned()]),
2441                script,
2442                globals.python_preference,
2443                globals.python_downloads,
2444                globals.concurrency,
2445                no_config,
2446                &cache,
2447                workspace_cache,
2448                printer,
2449                globals.preview,
2450            ))
2451            .await
2452        }
2453        ProjectCommand::Upgrade(args) => {
2454            // Resolve the settings from the command-line arguments and workspace configuration.
2455            let args = settings::UpgradeSettings::resolve(args, filesystem, environment);
2456            show_settings!(args);
2457
2458            // Initialize the cache.
2459            let cache = cache
2460                .init()
2461                .await?
2462                .with_refresh(Refresh::from(args.settings.upgrade.clone()));
2463
2464            Box::pin(commands::upgrade(
2465                project_dir,
2466                args.package,
2467                args.install_mirrors,
2468                args.settings,
2469                client_builder.subcommand(vec!["upgrade".to_owned()]),
2470                globals.python_preference,
2471                globals.python_downloads,
2472                globals.concurrency,
2473                no_config,
2474                &cache,
2475                workspace_cache,
2476                printer,
2477                globals.preview,
2478            ))
2479            .await
2480        }
2481        ProjectCommand::Add(args) => {
2482            // Resolve the settings from the command-line arguments and workspace configuration.
2483            let mut args = settings::AddSettings::resolve(args, filesystem, environment);
2484            show_settings!(args);
2485
2486            // If the script already exists, use it; otherwise, propagate the file path and we'll
2487            // initialize it later on.
2488            let script = script
2489                .map(|script| match script {
2490                    Pep723Item::Script(script) => script,
2491                    Pep723Item::Stdin(..) => unreachable!("`uv add` does not support stdin"),
2492                    Pep723Item::Remote(..) => {
2493                        unreachable!("`uv add` does not support remote files")
2494                    }
2495                })
2496                .map(ScriptPath::Script)
2497                .or(args.script.map(ScriptPath::Path));
2498
2499            let requirements = args
2500                .packages
2501                .iter()
2502                .map(String::as_str)
2503                .map(RequirementsSource::from_package_argument)
2504                .chain(
2505                    args.requirements
2506                        .into_iter()
2507                        .map(RequirementsSource::from_requirements_file),
2508                )
2509                .collect::<Result<Vec<_>>>()?;
2510
2511            // Special-case: any local source trees specified on the command-line are automatically
2512            // reinstalled.
2513            for requirement in &requirements {
2514                let requirement = match requirement {
2515                    RequirementsSource::Package(requirement) => requirement,
2516                    RequirementsSource::Editable(requirement) => requirement,
2517                    _ => continue,
2518                };
2519                match requirement {
2520                    RequirementsTxtRequirement::Named(requirement) => {
2521                        if let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() {
2522                            if let ParsedUrl::Directory(ParsedDirectoryUrl {
2523                                install_path, ..
2524                            }) = &url.parsed_url
2525                            {
2526                                debug!(
2527                                    "Marking explicit source tree for reinstall: `{}`",
2528                                    install_path.display()
2529                                );
2530                                args.settings.reinstall = args
2531                                    .settings
2532                                    .reinstall
2533                                    .with_package(requirement.name.clone());
2534                            }
2535                        }
2536                    }
2537                    RequirementsTxtRequirement::Unnamed(requirement) => {
2538                        if let ParsedUrl::Directory(ParsedDirectoryUrl { install_path, .. }) =
2539                            &requirement.url.parsed_url
2540                        {
2541                            debug!(
2542                                "Marking explicit source tree for reinstall: `{}`",
2543                                install_path.display()
2544                            );
2545                            args.settings.reinstall =
2546                                args.settings.reinstall.with_path(install_path.clone());
2547                        }
2548                    }
2549                }
2550            }
2551
2552            // Check for conflicts between offline and refresh.
2553            globals
2554                .network_settings
2555                .check_refresh_conflict(&args.refresh);
2556
2557            // Initialize the cache.
2558            let cache = cache.init().await?.with_refresh(
2559                args.refresh
2560                    .combine(Refresh::from(args.settings.reinstall.clone()))
2561                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2562            );
2563
2564            let constraints = args
2565                .constraints
2566                .into_iter()
2567                .map(RequirementsSource::from_constraints_txt)
2568                .collect::<Result<Vec<_>, _>>()?;
2569
2570            Box::pin(commands::add(
2571                project_dir,
2572                args.lock_check,
2573                args.frozen,
2574                args.active,
2575                args.no_sync,
2576                args.no_install_project,
2577                args.only_install_project,
2578                args.no_install_workspace,
2579                args.only_install_workspace,
2580                args.no_install_local,
2581                args.only_install_local,
2582                args.no_install_package,
2583                args.only_install_package,
2584                requirements,
2585                constraints,
2586                args.marker,
2587                args.editable,
2588                args.dependency_type,
2589                args.raw,
2590                args.bounds,
2591                args.indexes,
2592                args.rev,
2593                args.tag,
2594                args.branch,
2595                args.lfs,
2596                args.extras,
2597                args.package,
2598                args.python,
2599                args.workspace,
2600                args.install_mirrors,
2601                args.settings,
2602                client_builder.subcommand(vec!["add".to_owned()]),
2603                script,
2604                globals.python_preference,
2605                globals.python_downloads,
2606                globals.installer_metadata,
2607                globals.concurrency,
2608                no_config,
2609                &cache,
2610                printer,
2611                globals.preview,
2612                &args.malware_settings,
2613            ))
2614            .await
2615        }
2616        ProjectCommand::Remove(args) => {
2617            // Resolve the settings from the command-line arguments and workspace configuration.
2618            let args = settings::RemoveSettings::resolve(args, filesystem, environment);
2619            show_settings!(args);
2620
2621            // Check for conflicts between offline and refresh.
2622            globals
2623                .network_settings
2624                .check_refresh_conflict(&args.refresh);
2625
2626            // Initialize the cache.
2627            let cache = cache.init().await?.with_refresh(
2628                args.refresh
2629                    .combine(Refresh::from(args.settings.reinstall.clone()))
2630                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2631            );
2632
2633            // Unwrap the script.
2634            let script = script.map(|script| match script {
2635                Pep723Item::Script(script) => script,
2636                Pep723Item::Stdin(..) => unreachable!("`uv remove` does not support stdin"),
2637                Pep723Item::Remote(..) => unreachable!("`uv remove` does not support remote files"),
2638            });
2639
2640            Box::pin(commands::remove(
2641                project_dir,
2642                args.lock_check,
2643                args.frozen,
2644                args.active,
2645                args.no_sync,
2646                args.packages,
2647                args.dependency_type,
2648                args.package,
2649                args.python,
2650                args.install_mirrors,
2651                args.settings,
2652                client_builder.subcommand(vec!["remove".to_owned()]),
2653                script,
2654                globals.python_preference,
2655                globals.python_downloads,
2656                globals.installer_metadata,
2657                globals.concurrency,
2658                no_config,
2659                &cache,
2660                printer,
2661                globals.preview,
2662                args.malware_settings,
2663            ))
2664            .await
2665        }
2666        ProjectCommand::Version(args) => {
2667            // Resolve the settings from the command-line arguments and workspace configuration.
2668            let args = settings::VersionSettings::resolve(args, filesystem, environment);
2669            show_settings!(args);
2670
2671            // Check for conflicts between offline and refresh.
2672            globals
2673                .network_settings
2674                .check_refresh_conflict(&args.refresh);
2675
2676            // Initialize the cache.
2677            let cache = cache.init().await?.with_refresh(
2678                args.refresh
2679                    .combine(Refresh::from(args.settings.reinstall.clone()))
2680                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2681            );
2682
2683            Box::pin(commands::project_version(
2684                args.value,
2685                args.bump,
2686                args.short,
2687                args.output_format,
2688                project_dir,
2689                args.package,
2690                explicit_project,
2691                args.dry_run,
2692                args.lock_check,
2693                args.frozen,
2694                args.active,
2695                args.no_sync,
2696                args.python,
2697                args.install_mirrors,
2698                args.settings,
2699                client_builder.subcommand(vec!["version".to_owned()]),
2700                globals.python_preference,
2701                globals.python_downloads,
2702                globals.installer_metadata,
2703                globals.concurrency,
2704                no_config,
2705                &cache,
2706                workspace_cache,
2707                printer,
2708                globals.preview,
2709                args.malware_settings,
2710            ))
2711            .await
2712        }
2713        ProjectCommand::Tree(args) => {
2714            // Resolve the settings from the command-line arguments and workspace configuration.
2715            let args = settings::TreeSettings::resolve(args, filesystem, environment);
2716            show_settings!(args);
2717
2718            // Initialize the cache.
2719            let cache = cache.init().await?;
2720
2721            // Unwrap the script.
2722            let script = script.map(|script| match script {
2723                Pep723Item::Script(script) => script,
2724                Pep723Item::Stdin(..) => unreachable!("`uv tree` does not support stdin"),
2725                Pep723Item::Remote(..) => unreachable!("`uv tree` does not support remote files"),
2726            });
2727
2728            Box::pin(commands::tree(
2729                project_dir,
2730                args.groups,
2731                args.lock_check,
2732                args.frozen,
2733                args.universal,
2734                args.depth,
2735                args.prune,
2736                args.package,
2737                args.no_dedupe,
2738                args.invert,
2739                args.outdated,
2740                args.show_sizes,
2741                args.python_version,
2742                args.python_platform,
2743                args.python,
2744                args.install_mirrors,
2745                args.resolver,
2746                &client_builder.subcommand(vec!["tree".to_owned()]),
2747                script,
2748                globals.python_preference,
2749                globals.python_downloads,
2750                globals.concurrency,
2751                no_config,
2752                &cache,
2753                printer,
2754                globals.preview,
2755            ))
2756            .await
2757        }
2758        ProjectCommand::Export(args) => {
2759            // Resolve the settings from the command-line arguments and workspace configuration.
2760            let args = settings::ExportSettings::resolve(args, filesystem, environment);
2761            show_settings!(args);
2762
2763            // Initialize the cache.
2764            let cache = cache.init().await?;
2765
2766            // Unwrap the script.
2767            let script = script.map(|script| match script {
2768                Pep723Item::Script(script) => script,
2769                Pep723Item::Stdin(..) => unreachable!("`uv export` does not support stdin"),
2770                Pep723Item::Remote(..) => unreachable!("`uv export` does not support remote files"),
2771            });
2772
2773            commands::export(
2774                project_dir,
2775                args.format,
2776                args.all_packages,
2777                args.package,
2778                args.prune,
2779                args.hashes,
2780                args.install_options,
2781                args.output_file,
2782                args.extras,
2783                args.groups,
2784                args.editable,
2785                args.lock_check,
2786                args.frozen,
2787                args.include_annotations,
2788                args.include_header,
2789                args.include_index_url,
2790                args.include_find_links,
2791                script,
2792                args.python,
2793                args.install_mirrors,
2794                args.settings,
2795                client_builder.subcommand(vec!["export".to_owned()]),
2796                globals.python_preference,
2797                globals.python_downloads,
2798                globals.concurrency,
2799                no_config,
2800                globals.quiet > 0,
2801                &cache,
2802                printer,
2803                globals.preview,
2804            )
2805            .boxed_local()
2806            .await
2807        }
2808        ProjectCommand::Format(args) => {
2809            // Resolve the settings from the command-line arguments and workspace configuration.
2810            let args = settings::FormatSettings::resolve(args, filesystem, environment);
2811            show_settings!(args);
2812
2813            // Initialize the cache.
2814            let cache = cache.init().await?;
2815
2816            Box::pin(commands::format(
2817                project_dir,
2818                args.ruff_path,
2819                args.check,
2820                args.diff,
2821                args.extra_args,
2822                args.version,
2823                args.exclude_newer,
2824                args.show_version,
2825                client_builder.subcommand(vec!["format".to_owned()]),
2826                cache,
2827                printer,
2828                globals.preview,
2829                args.no_project,
2830            ))
2831            .await
2832        }
2833        ProjectCommand::Check(args) => {
2834            // Resolve the settings from the command-line arguments and workspace configuration.
2835            let args = settings::CheckSettings::resolve(args, filesystem, environment);
2836            show_settings!(args);
2837
2838            // Check for conflicts between offline and refresh.
2839            globals
2840                .network_settings
2841                .check_refresh_conflict(&args.refresh);
2842
2843            // Initialize the cache.
2844            let cache = cache.init().await?.with_refresh(
2845                args.refresh
2846                    .combine(Refresh::from(args.settings.reinstall.clone()))
2847                    .combine(Refresh::from(args.settings.resolver.upgrade.clone())),
2848            );
2849
2850            let script = script.and_then(|script| match script {
2851                Pep723Item::Script(script) => Some(script),
2852                Pep723Item::Remote(..) | Pep723Item::Stdin(..) => None,
2853            });
2854
2855            Box::pin(commands::check(
2856                project_dir,
2857                args.ty_path,
2858                args.lock_check,
2859                args.frozen,
2860                args.no_sync,
2861                args.isolated,
2862                args.extras,
2863                args.groups,
2864                args.python,
2865                args.install_mirrors,
2866                args.settings,
2867                args.ty_version,
2868                args.show_version,
2869                script,
2870                client_builder.subcommand(vec!["check".to_owned()]),
2871                globals.python_preference,
2872                globals.python_downloads,
2873                globals.installer_metadata,
2874                globals.concurrency,
2875                &cache,
2876                workspace_cache,
2877                printer,
2878                globals.preview,
2879                args.no_project,
2880                no_config,
2881                args.malware_settings,
2882            ))
2883            .await
2884        }
2885        ProjectCommand::Audit(audit_args) => {
2886            let args = settings::AuditSettings::resolve(audit_args, filesystem, environment);
2887            show_settings!(args);
2888
2889            // Initialize the cache.
2890            let cache = cache.init().await?;
2891
2892            // Unwrap the script.
2893            let script = script.map(|script| match script {
2894                Pep723Item::Script(script) => script,
2895                Pep723Item::Stdin(..) => unreachable!("`uv audit` does not support stdin"),
2896                Pep723Item::Remote(..) => unreachable!("`uv audit` does not support remote files"),
2897            });
2898
2899            Box::pin(commands::audit(
2900                project_dir,
2901                args.extras,
2902                args.groups,
2903                args.lock_check,
2904                args.frozen,
2905                script,
2906                args.python_version,
2907                args.python_platform,
2908                args.install_mirrors,
2909                args.settings,
2910                client_builder.subcommand(vec!["audit".to_owned()]),
2911                globals.python_preference,
2912                globals.python_downloads,
2913                globals.concurrency,
2914                no_config,
2915                cache,
2916                printer,
2917                globals.preview,
2918                args.output_format,
2919                args.service_format,
2920                args.service_url,
2921                args.ignore,
2922                args.ignore_until_fixed,
2923            ))
2924            .await
2925        }
2926    }
2927}
2928
2929/// Hint users who used `uv <subcommand>` when they meant `uv pip <subcommand>`.
2930fn suggest_subcommand(err: &mut Error) {
2931    if let Some(ContextValue::String(subcommand)) = err.get(ContextKind::InvalidSubcommand) {
2932        match subcommand.as_str() {
2933            "compile" => {
2934                err.insert(
2935                    ContextKind::SuggestedSubcommand,
2936                    ContextValue::String("uv pip compile".to_string()),
2937                );
2938            }
2939            "install" => {
2940                err.insert(
2941                    ContextKind::SuggestedSubcommand,
2942                    ContextValue::String("uv pip install".to_string()),
2943                );
2944            }
2945            "uninstall" => {
2946                err.insert(
2947                    ContextKind::SuggestedSubcommand,
2948                    ContextValue::String("uv pip uninstall".to_string()),
2949                );
2950            }
2951            "freeze" => {
2952                err.insert(
2953                    ContextKind::SuggestedSubcommand,
2954                    ContextValue::String("uv pip freeze".to_string()),
2955                );
2956            }
2957            "list" => {
2958                err.insert(
2959                    ContextKind::SuggestedSubcommand,
2960                    ContextValue::String("uv pip list".to_string()),
2961                );
2962            }
2963            "show" => {
2964                err.insert(
2965                    ContextKind::SuggestedSubcommand,
2966                    ContextValue::String("uv pip show".to_string()),
2967                );
2968            }
2969            _ => {}
2970        }
2971    }
2972}
2973
2974/// The main entry point for a uv invocation.
2975///
2976/// # Usage
2977///
2978/// This entry point is not recommended for external consumption, the uv binary interface is the
2979/// official public API.
2980///
2981/// When using this entry point, uv assumes it is running in a process it controls and that the
2982/// entire process lifetime is managed by uv. Unexpected behavior may be encountered if this entry
2983/// point is called multiple times in a single process.
2984///
2985/// # Safety
2986///
2987/// It is only safe to call this routine when it is known that multiple threads are not running.
2988#[allow(unsafe_code)]
2989pub unsafe fn main<I, T>(args: I) -> ExitCode
2990where
2991    I: IntoIterator<Item = T>,
2992    T: Into<OsString> + Clone,
2993{
2994    #[cfg(windows)]
2995    uv_windows::install_unhandled_exception_handler();
2996
2997    // Set the `UV` variable to the current executable so it is implicitly propagated to all child
2998    // processes, e.g., in `uv run`.
2999    if let Ok(current_exe) = std::env::current_exe() {
3000        // SAFETY: The proof obligation must be satisfied by the caller.
3001        unsafe {
3002            // This will become unsafe in Rust 2024
3003            // See https://doc.rust-lang.org/std/env/fn.set_var.html#safety
3004            std::env::set_var(EnvVars::UV, current_exe);
3005        }
3006    }
3007
3008    // `std::env::args` is not `Send` so we parse before passing to our runtime
3009    // https://github.com/rust-lang/rust/pull/48005
3010    let cli = match Cli::try_parse_from(args) {
3011        Ok(cli) => cli,
3012        Err(mut err) => {
3013            suggest_subcommand(&mut err);
3014            err.exit()
3015        }
3016    };
3017
3018    // Configure a printer for failures that escape command execution. The resolved `no_progress`
3019    // setting can differ due to environment variables, but it does not affect important stderr.
3020    let printer = Printer::new(
3021        cli.top_level.global_args.quiet,
3022        cli.top_level.global_args.verbose,
3023        cli.top_level.global_args.no_progress,
3024    );
3025
3026    // See `min_stack_size` doc comment about `main2`
3027    let min_stack_size = min_stack_size();
3028    let main2 = move || {
3029        let runtime = tokio::runtime::Builder::new_current_thread()
3030            .enable_all()
3031            .thread_stack_size(min_stack_size)
3032            .build()
3033            .expect("Failed building the Runtime");
3034        // Box the large main future to avoid stack overflows.
3035        let result = runtime.block_on(Box::pin(run(cli, GlobalInitialization::Initialize)));
3036        // Avoid waiting for pending tasks to complete.
3037        //
3038        // The resolver may have kicked off HTTP requests during resolution that
3039        // turned out to be unnecessary. Waiting for those to complete can cause
3040        // the CLI to hang before exiting.
3041        runtime.shutdown_background();
3042        result
3043    };
3044    let result = std::thread::Builder::new()
3045        .name("main2".to_owned())
3046        .stack_size(min_stack_size)
3047        .spawn(main2)
3048        .expect("Tokio executor failed, was there a panic?")
3049        .join()
3050        .expect("Tokio executor failed, was there a panic?");
3051
3052    match result {
3053        Ok(code) => code.into(),
3054        Err(err) => {
3055            let error = match err.downcast::<UvError>() {
3056                Ok(error) => error,
3057                Err(err) => UvError::unexpected(err),
3058            };
3059            match error {
3060                UvError::User(err) => {
3061                    commands::diagnostics::write_error_chain(&err, printer)
3062                        .expect("writing to stderr should not fail");
3063                    ExitStatus::Failure.into()
3064                }
3065                UvError::Unexpected(err) => {
3066                    trace!(
3067                        "Error chain:\n{}",
3068                        uv_errors::debug_error_chain(err.as_ref())
3069                    );
3070                    if err.backtrace().status() == std::backtrace::BacktraceStatus::Captured {
3071                        trace!("Error backtrace:\n{}", err.backtrace());
3072                    }
3073                    commands::diagnostics::write_error_chain(&err, printer)
3074                        .expect("writing to stderr should not fail");
3075                    ExitStatus::Error.into()
3076                }
3077            }
3078        }
3079    }
3080}