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