Skip to main content

uv_cli/
options.rs

1use std::env;
2use std::error::Error;
3use std::fmt;
4
5use anyhow::bail;
6
7use uv_cache::Refresh;
8use uv_configuration::{
9    BuildIsolation, ExcludeNewerPackage, PrereleaseMode, PrereleasePackage, Reinstall, Upgrade,
10};
11use uv_distribution_types::{ConfigSettings, Index, PackageConfigSettings, Requirement};
12use uv_normalize::PackageName;
13use uv_settings::{
14    Combine, EnvFlag, IndexOptions, PipOptions, ResolverInstallerOptions, ResolverOptions,
15};
16use uv_warnings::owo_colors::OwoColorize;
17
18use crate::{
19    BuildIsolationArgs, BuildOptionsArgs, CompileBytecodeArgs, ExcludeNewerArgs, FetchArgs,
20    IndexArgs, InstallerArgs, Maybe, PackageBuildIsolationArgs, PackageExcludeNewerArgs,
21    RefreshArgs, RegistryClientArgs, ReinstallArgs, ResolverArgs, ResolverInstallerArgs,
22    SourcesArgs, UpgradeArgs, VersionSelectionArgs,
23};
24
25/// An error caused by an invalid combination of command-line arguments.
26#[derive(Debug)]
27pub struct ArgumentError(String);
28
29impl fmt::Display for ArgumentError {
30    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31        self.0.fmt(formatter)
32    }
33}
34
35impl Error for ArgumentError {}
36
37/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
38pub(crate) fn flag(yes: bool, no: bool, name: &str) -> anyhow::Result<Option<bool>> {
39    debug_assert!(
40        !name.starts_with("no-"),
41        "flag names must not include the `no-` prefix"
42    );
43
44    match (yes, no) {
45        (true, false) => Ok(Some(true)),
46        (false, true) => Ok(Some(false)),
47        (false, false) => Ok(None),
48        (..) => {
49            bail!(ArgumentError(format!(
50                "`{}` and `{}` cannot be used together. \
51                Boolean flags on different levels are currently not supported \
52                (https://github.com/clap-rs/clap/issues/6049)",
53                format!("--{name}").green(),
54                format!("--no-{name}").green(),
55            )));
56        }
57    }
58}
59
60/// The source of a boolean flag value.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub(crate) enum FlagSource {
63    /// The flag was set via command-line argument.
64    Cli,
65    /// The flag was set via environment variable.
66    Env(&'static str),
67    /// The flag was set via workspace/project configuration.
68    Config,
69}
70
71impl fmt::Display for FlagSource {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::Cli => write!(f, "command-line argument"),
75            Self::Env(name) => write!(f, "environment variable `{name}`"),
76            Self::Config => write!(f, "workspace configuration"),
77        }
78    }
79}
80
81/// A boolean flag value with its source.
82#[derive(Debug, Clone, Copy)]
83pub(crate) enum Flag {
84    /// The flag is not set.
85    Disabled,
86    /// The flag is enabled with a known source.
87    Enabled {
88        source: FlagSource,
89        /// The CLI flag name (e.g., "locked" for `--locked`).
90        name: &'static str,
91    },
92}
93
94impl Flag {
95    /// Create a flag that is explicitly disabled.
96    pub(crate) const fn disabled() -> Self {
97        Self::Disabled
98    }
99
100    /// Create an enabled flag from a CLI argument.
101    pub(crate) const fn from_cli(name: &'static str) -> Self {
102        Self::Enabled {
103            source: FlagSource::Cli,
104            name,
105        }
106    }
107
108    /// Create an enabled flag from workspace/project configuration.
109    pub(crate) const fn from_config(name: &'static str) -> Self {
110        Self::Enabled {
111            source: FlagSource::Config,
112            name,
113        }
114    }
115
116    /// Returns `true` if the flag is set.
117    pub(crate) fn is_enabled(self) -> bool {
118        matches!(self, Self::Enabled { .. })
119    }
120}
121
122impl From<Flag> for bool {
123    fn from(flag: Flag) -> Self {
124        flag.is_enabled()
125    }
126}
127
128/// Resolve a boolean flag from CLI arguments and an environment variable.
129///
130/// The CLI argument takes precedence over the environment variable. Returns a [`Flag`] with the
131/// resolved value and source.
132pub(crate) fn resolve_flag(cli_flag: bool, name: &'static str, env_flag: EnvFlag) -> Flag {
133    if cli_flag {
134        Flag::Enabled {
135            source: FlagSource::Cli,
136            name,
137        }
138    } else if env_flag.value == Some(true) {
139        Flag::Enabled {
140            source: FlagSource::Env(env_flag.env_var),
141            name,
142        }
143    } else {
144        Flag::Disabled
145    }
146}
147
148/// Resolve a pair of mutually exclusive boolean flags from the CLI and environment variables.
149///
150/// If either flag is set on the command line, both environment variables are ignored so the CLI
151/// retains precedence over the full pair.
152pub(crate) fn resolve_flag_pair(
153    cli_flag: bool,
154    cli_no_flag: bool,
155    name: &'static str,
156    no_name: &'static str,
157    env_flag: Option<EnvFlag>,
158    env_no_flag: Option<EnvFlag>,
159) -> (Flag, Flag) {
160    if cli_flag || cli_no_flag {
161        (
162            if cli_flag {
163                Flag::from_cli(name)
164            } else {
165                Flag::disabled()
166            },
167            if cli_no_flag {
168                Flag::from_cli(no_name)
169            } else {
170                Flag::disabled()
171            },
172        )
173    } else {
174        (
175            env_flag.map_or_else(Flag::disabled, |env_flag| {
176                resolve_flag(false, name, env_flag)
177            }),
178            env_no_flag.map_or_else(Flag::disabled, |env_no_flag| {
179                resolve_flag(false, no_name, env_no_flag)
180            }),
181        )
182    }
183}
184
185/// Check if two flags conflict and return an error if they do.
186///
187/// This function checks if both flags are enabled (truthy) and reports an error if so, including
188/// the source of each flag (CLI or environment variable) in the error message.
189pub(crate) fn check_conflicts(flag_a: Flag, flag_b: Flag) -> anyhow::Result<()> {
190    if let (
191        Flag::Enabled {
192            source: source_a,
193            name: name_a,
194        },
195        Flag::Enabled {
196            source: source_b,
197            name: name_b,
198        },
199    ) = (flag_a, flag_b)
200    {
201        let display_a = match source_a {
202            FlagSource::Cli => format!("`--{name_a}`"),
203            FlagSource::Env(env) => format!("`{env}` (environment variable)"),
204            FlagSource::Config => format!("`{name_a}` (workspace configuration)"),
205        };
206        let display_b = match source_b {
207            FlagSource::Cli => format!("`--{name_b}`"),
208            FlagSource::Env(env) => format!("`{env}` (environment variable)"),
209            FlagSource::Config => format!("`{name_b}` (workspace configuration)"),
210        };
211        bail!(ArgumentError(format!(
212            "the argument {} cannot be used with {}",
213            display_a.green(),
214            display_b.green()
215        )));
216    }
217    Ok(())
218}
219
220impl TryFrom<RefreshArgs> for Refresh {
221    type Error = anyhow::Error;
222
223    fn try_from(value: RefreshArgs) -> anyhow::Result<Self> {
224        let RefreshArgs {
225            refresh,
226            no_refresh,
227            refresh_package,
228        } = value;
229
230        Ok(Self::from_args(
231            flag(refresh, no_refresh, "refresh")?,
232            refresh_package,
233        ))
234    }
235}
236
237/// Convert command-line arguments into [`PipOptions`].
238pub(crate) trait IntoPipOptions {
239    /// Convert command-line arguments into pip options using the effective configuration.
240    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions>;
241}
242
243impl IntoPipOptions for ResolverArgs {
244    /// Convert resolver arguments into pip options using the effective configuration.
245    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions> {
246        let Self {
247            index_args,
248            upgrade,
249            no_upgrade,
250            upgrade_package,
251            upgrade_group,
252            registry_client:
253                RegistryClientArgs {
254                    index_strategy,
255                    keyring_provider,
256                },
257            version_selection:
258                VersionSelectionArgs {
259                    resolution,
260                    prerelease,
261                    prerelease_package,
262                    pre,
263                    fork_strategy,
264                },
265            config_setting,
266            config_settings_package,
267            build_isolation:
268                PackageBuildIsolationArgs {
269                    build_isolation:
270                        BuildIsolationArgs {
271                            no_build_isolation,
272                            build_isolation,
273                        },
274                    no_build_isolation_package,
275                },
276            exclude_newer:
277                PackageExcludeNewerArgs {
278                    exclude_newer: ExcludeNewerArgs { exclude_newer },
279                    exclude_newer_package,
280                },
281            link_mode,
282            sources:
283                SourcesArgs {
284                    no_sources,
285                    no_sources_package,
286                },
287        } = self;
288
289        if !upgrade_group.is_empty() {
290            bail!(ArgumentError(format!(
291                "`{}` is not supported in `uv pip` commands",
292                "--upgrade-group".green()
293            )));
294        }
295
296        Ok(PipOptions {
297            upgrade: flag(upgrade, no_upgrade, "upgrade")?,
298            upgrade_package: Some(upgrade_package),
299            index_strategy,
300            keyring_provider,
301            resolution,
302            fork_strategy,
303            prerelease: if pre {
304                Some(PrereleaseMode::Allow)
305            } else {
306                prerelease
307            },
308            prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
309            config_settings: config_setting
310                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
311            config_settings_package: config_settings_package.map(|config_settings| {
312                config_settings
313                    .into_iter()
314                    .collect::<PackageConfigSettings>()
315            }),
316            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
317            no_build_isolation_package: Some(no_build_isolation_package),
318            exclude_newer,
319            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
320            link_mode,
321            no_sources: if no_sources { Some(true) } else { None },
322            no_sources_package: if no_sources_package.is_empty() {
323                None
324            } else {
325                Some(no_sources_package)
326            },
327            ..index_args.into_pip_options(configured_indexes)?
328        })
329    }
330}
331
332impl IntoPipOptions for InstallerArgs {
333    /// Convert installer arguments into pip options using the effective configuration.
334    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions> {
335        let Self {
336            index_args,
337            reinstall:
338                ReinstallArgs {
339                    reinstall,
340                    no_reinstall,
341                    reinstall_package,
342                },
343            registry_client:
344                RegistryClientArgs {
345                    index_strategy,
346                    keyring_provider,
347                },
348            config_setting,
349            config_settings_package,
350            build_isolation:
351                BuildIsolationArgs {
352                    no_build_isolation,
353                    build_isolation,
354                },
355            exclude_newer:
356                PackageExcludeNewerArgs {
357                    exclude_newer: ExcludeNewerArgs { exclude_newer },
358                    exclude_newer_package,
359                },
360            link_mode,
361            compile_bytecode:
362                CompileBytecodeArgs {
363                    compile_bytecode,
364                    no_compile_bytecode,
365                },
366            sources:
367                SourcesArgs {
368                    no_sources,
369                    no_sources_package,
370                },
371        } = self;
372
373        Ok(PipOptions {
374            reinstall: flag(reinstall, no_reinstall, "reinstall")?,
375            reinstall_package: Some(reinstall_package),
376            index_strategy,
377            keyring_provider,
378            config_settings: config_setting
379                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
380            config_settings_package: config_settings_package.map(|config_settings| {
381                config_settings
382                    .into_iter()
383                    .collect::<PackageConfigSettings>()
384            }),
385            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
386            exclude_newer,
387            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
388            link_mode,
389            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
390            no_sources: if no_sources { Some(true) } else { None },
391            no_sources_package: if no_sources_package.is_empty() {
392                None
393            } else {
394                Some(no_sources_package)
395            },
396            ..index_args.into_pip_options(configured_indexes)?
397        })
398    }
399}
400
401impl IntoPipOptions for ResolverInstallerArgs {
402    /// Convert resolver and installer arguments into pip options using the effective configuration.
403    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions> {
404        let Self {
405            index_args,
406            upgrade,
407            no_upgrade,
408            upgrade_package,
409            upgrade_group,
410            reinstall:
411                ReinstallArgs {
412                    reinstall,
413                    no_reinstall,
414                    reinstall_package,
415                },
416            registry_client:
417                RegistryClientArgs {
418                    index_strategy,
419                    keyring_provider,
420                },
421            version_selection:
422                VersionSelectionArgs {
423                    resolution,
424                    prerelease,
425                    prerelease_package,
426                    pre,
427                    fork_strategy,
428                },
429            config_setting,
430            config_settings_package,
431            build_isolation:
432                PackageBuildIsolationArgs {
433                    build_isolation:
434                        BuildIsolationArgs {
435                            no_build_isolation,
436                            build_isolation,
437                        },
438                    no_build_isolation_package,
439                },
440            exclude_newer:
441                PackageExcludeNewerArgs {
442                    exclude_newer: ExcludeNewerArgs { exclude_newer },
443                    exclude_newer_package,
444                },
445            link_mode,
446            compile_bytecode:
447                CompileBytecodeArgs {
448                    compile_bytecode,
449                    no_compile_bytecode,
450                },
451            sources:
452                SourcesArgs {
453                    no_sources,
454                    no_sources_package,
455                },
456        } = self;
457
458        if !upgrade_group.is_empty() {
459            bail!(ArgumentError(format!(
460                "`{}` is not supported in `uv pip` commands",
461                "--upgrade-group".green()
462            )));
463        }
464
465        Ok(PipOptions {
466            upgrade: flag(upgrade, no_upgrade, "upgrade")?,
467            upgrade_package: Some(upgrade_package),
468            reinstall: flag(reinstall, no_reinstall, "reinstall")?,
469            reinstall_package: Some(reinstall_package),
470            index_strategy,
471            keyring_provider,
472            resolution,
473            prerelease: if pre {
474                Some(PrereleaseMode::Allow)
475            } else {
476                prerelease
477            },
478            prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
479            fork_strategy,
480            config_settings: config_setting
481                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
482            config_settings_package: config_settings_package.map(|config_settings| {
483                config_settings
484                    .into_iter()
485                    .collect::<PackageConfigSettings>()
486            }),
487            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
488            no_build_isolation_package: Some(no_build_isolation_package),
489            exclude_newer,
490            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
491            link_mode,
492            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
493            no_sources: if no_sources { Some(true) } else { None },
494            no_sources_package: if no_sources_package.is_empty() {
495                None
496            } else {
497                Some(no_sources_package)
498            },
499            ..index_args.into_pip_options(configured_indexes)?
500        })
501    }
502}
503
504impl IntoPipOptions for FetchArgs {
505    /// Convert package-fetch arguments into pip options using the effective configuration.
506    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions> {
507        let Self {
508            index_args,
509            registry_client:
510                RegistryClientArgs {
511                    index_strategy,
512                    keyring_provider,
513                },
514            exclude_newer:
515                PackageExcludeNewerArgs {
516                    exclude_newer: ExcludeNewerArgs { exclude_newer },
517                    exclude_newer_package,
518                },
519        } = self;
520
521        Ok(PipOptions {
522            index_strategy,
523            keyring_provider,
524            exclude_newer,
525            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
526            ..index_args.into_pip_options(configured_indexes)?
527        })
528    }
529}
530
531impl IndexArgs {
532    /// Resolve the index arguments shared by pip, resolver, and installer settings.
533    fn resolve(self, configured_indexes: &[Index]) -> anyhow::Result<IndexOptions> {
534        let Self {
535            default_index,
536            index,
537            index_url,
538            extra_index_url,
539            no_index,
540            find_links,
541        } = self;
542
543        let default_index = default_index
544            .and_then(Maybe::into_option)
545            .map(|index| index.resolve(configured_indexes))
546            .transpose()?
547            .map(|index| vec![index]);
548        let index = index
549            .map(|indexes| {
550                indexes
551                    .into_iter()
552                    .flatten()
553                    .filter_map(Maybe::into_option)
554                    .map(|index| index.resolve(configured_indexes))
555                    .collect::<anyhow::Result<Vec<_>>>()
556            })
557            .transpose()?;
558
559        Ok(IndexOptions {
560            index: default_index.combine(index),
561            index_url: index_url.and_then(Maybe::into_option),
562            extra_index_url: extra_index_url
563                .map(|indexes| indexes.into_iter().filter_map(Maybe::into_option).collect()),
564            no_index: no_index.then_some(true),
565            find_links: find_links
566                .map(|links| links.into_iter().filter_map(Maybe::into_option).collect()),
567        })
568    }
569}
570
571impl IntoPipOptions for IndexArgs {
572    /// Convert index arguments into pip options, resolving configured index names.
573    fn into_pip_options(self, configured_indexes: &[Index]) -> anyhow::Result<PipOptions> {
574        Ok(PipOptions::from(
575            self.resolve(configured_indexes)?
576                .relative_to(&env::current_dir()?)?,
577        ))
578    }
579}
580
581/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildOptionsArgs`].
582pub(crate) fn resolver_options(
583    resolver_args: ResolverArgs,
584    build_args: BuildOptionsArgs,
585    configured_indexes: &[Index],
586) -> anyhow::Result<ResolverOptions> {
587    let ResolverArgs {
588        index_args,
589        upgrade,
590        no_upgrade,
591        upgrade_package,
592        upgrade_group,
593        registry_client:
594            RegistryClientArgs {
595                index_strategy,
596                keyring_provider,
597            },
598        version_selection:
599            VersionSelectionArgs {
600                resolution,
601                prerelease,
602                prerelease_package,
603                pre,
604                fork_strategy,
605            },
606        config_setting,
607        config_settings_package,
608        build_isolation:
609            PackageBuildIsolationArgs {
610                build_isolation:
611                    BuildIsolationArgs {
612                        no_build_isolation,
613                        build_isolation,
614                    },
615                no_build_isolation_package,
616            },
617        exclude_newer:
618            PackageExcludeNewerArgs {
619                exclude_newer: ExcludeNewerArgs { exclude_newer },
620                exclude_newer_package,
621            },
622        link_mode,
623        sources: SourcesArgs {
624            no_sources,
625            no_sources_package,
626        },
627    } = resolver_args;
628
629    let BuildOptionsArgs {
630        no_build,
631        build,
632        no_build_package,
633        no_binary,
634        binary,
635        no_binary_package,
636        require_build_hashes,
637        no_require_build_hashes,
638    } = build_args;
639
640    ResolverOptions {
641        indexes: index_args.resolve(configured_indexes)?,
642        upgrade: Upgrade::from_args(
643            flag(upgrade, no_upgrade, "upgrade")?,
644            upgrade_package.into_iter().map(Requirement::from).collect(),
645            upgrade_group,
646        ),
647        index_strategy,
648        keyring_provider,
649        resolution,
650        prerelease: if pre {
651            Some(PrereleaseMode::Allow)
652        } else {
653            prerelease
654        },
655        prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
656        fork_strategy,
657        dependency_metadata: None,
658        config_settings: config_setting
659            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
660        config_settings_package: config_settings_package.map(|config_settings| {
661            config_settings
662                .into_iter()
663                .collect::<PackageConfigSettings>()
664        }),
665        build_isolation: BuildIsolation::from_args(
666            flag(no_build_isolation, build_isolation, "build-isolation")?,
667            no_build_isolation_package,
668        ),
669        require_build_hashes: flag(
670            require_build_hashes,
671            no_require_build_hashes,
672            "require-build-hashes",
673        )?,
674        extra_build_dependencies: None,
675        extra_build_variables: None,
676        exclude_newer,
677        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
678        link_mode,
679        torch_backend: None,
680        no_build: flag(no_build, build, "build")?,
681        no_build_package: if no_build_package.is_empty() {
682            None
683        } else {
684            Some(no_build_package)
685        },
686        no_binary: flag(no_binary, binary, "binary")?,
687        no_binary_package: if no_binary_package.is_empty() {
688            None
689        } else {
690            Some(no_binary_package)
691        },
692        no_sources: if no_sources { Some(true) } else { None },
693        no_sources_package: if no_sources_package.is_empty() {
694            None
695        } else {
696            Some(no_sources_package)
697        },
698    }
699    .relative_to(&env::current_dir()?)
700    .map_err(Into::into)
701}
702
703/// Construct the [`ResolverOptions`] for an [`UpgradeArgs`] invocation.
704pub(crate) fn upgrade_options(
705    args: UpgradeArgs,
706    configured_indexes: &[Index],
707) -> anyhow::Result<(Vec<PackageName>, Vec<PackageName>, ResolverOptions)> {
708    let UpgradeArgs {
709        packages,
710        exclude,
711        require_build_hashes,
712        no_require_build_hashes,
713        index_args,
714        registry_client:
715            RegistryClientArgs {
716                index_strategy,
717                keyring_provider,
718            },
719    } = args;
720
721    let options = ResolverOptions {
722        indexes: index_args.resolve(configured_indexes)?,
723        index_strategy,
724        keyring_provider,
725        require_build_hashes: flag(
726            require_build_hashes,
727            no_require_build_hashes,
728            "require-build-hashes",
729        )?,
730        ..ResolverOptions::default()
731    }
732    .relative_to(&env::current_dir()?)?;
733
734    Ok((packages, exclude, options))
735}
736
737/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildOptionsArgs`].
738pub(crate) fn resolver_installer_options(
739    resolver_installer_args: ResolverInstallerArgs,
740    build_args: BuildOptionsArgs,
741    configured_indexes: &[Index],
742) -> anyhow::Result<ResolverInstallerOptions> {
743    let ResolverInstallerArgs {
744        index_args,
745        upgrade,
746        no_upgrade,
747        upgrade_package,
748        upgrade_group,
749        reinstall:
750            ReinstallArgs {
751                reinstall,
752                no_reinstall,
753                reinstall_package,
754            },
755        registry_client:
756            RegistryClientArgs {
757                index_strategy,
758                keyring_provider,
759            },
760        version_selection:
761            VersionSelectionArgs {
762                resolution,
763                prerelease,
764                prerelease_package,
765                pre,
766                fork_strategy,
767            },
768        config_setting,
769        config_settings_package,
770        build_isolation:
771            PackageBuildIsolationArgs {
772                build_isolation:
773                    BuildIsolationArgs {
774                        no_build_isolation,
775                        build_isolation,
776                    },
777                no_build_isolation_package,
778            },
779        exclude_newer:
780            PackageExcludeNewerArgs {
781                exclude_newer: ExcludeNewerArgs { exclude_newer },
782                exclude_newer_package,
783            },
784        link_mode,
785        compile_bytecode:
786            CompileBytecodeArgs {
787                compile_bytecode,
788                no_compile_bytecode,
789            },
790        sources: SourcesArgs {
791            no_sources,
792            no_sources_package,
793        },
794    } = resolver_installer_args;
795
796    let BuildOptionsArgs {
797        no_build,
798        build,
799        no_build_package,
800        no_binary,
801        binary,
802        no_binary_package,
803        require_build_hashes,
804        no_require_build_hashes,
805    } = build_args;
806
807    ResolverInstallerOptions {
808        indexes: index_args.resolve(configured_indexes)?,
809        upgrade: Upgrade::from_args(
810            flag(upgrade, no_upgrade, "upgrade")?,
811            upgrade_package.into_iter().map(Requirement::from).collect(),
812            upgrade_group,
813        ),
814        reinstall: Reinstall::from_args(
815            flag(reinstall, no_reinstall, "reinstall")?,
816            reinstall_package,
817        ),
818        index_strategy,
819        keyring_provider,
820        resolution,
821        prerelease: if pre {
822            Some(PrereleaseMode::Allow)
823        } else {
824            prerelease
825        },
826        prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
827        fork_strategy,
828        dependency_metadata: None,
829        config_settings: config_setting
830            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
831        config_settings_package: config_settings_package.map(|config_settings| {
832            config_settings
833                .into_iter()
834                .collect::<PackageConfigSettings>()
835        }),
836        build_isolation: BuildIsolation::from_args(
837            flag(no_build_isolation, build_isolation, "build-isolation")?,
838            no_build_isolation_package,
839        ),
840        require_build_hashes: flag(
841            require_build_hashes,
842            no_require_build_hashes,
843            "require-build-hashes",
844        )?,
845        extra_build_dependencies: None,
846        extra_build_variables: None,
847        exclude_newer,
848        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
849        link_mode,
850        compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
851        no_build: flag(no_build, build, "build")?,
852        no_build_package: if no_build_package.is_empty() {
853            None
854        } else {
855            Some(no_build_package)
856        },
857        no_binary: flag(no_binary, binary, "binary")?,
858        no_binary_package: if no_binary_package.is_empty() {
859            None
860        } else {
861            Some(no_binary_package)
862        },
863        no_sources: if no_sources { Some(true) } else { None },
864        no_sources_package: if no_sources_package.is_empty() {
865            None
866        } else {
867            Some(no_sources_package)
868        },
869        torch_backend: None,
870    }
871    .relative_to(&env::current_dir()?)
872    .map_err(Into::into)
873}