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 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 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 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 const fn disabled() -> Self {
97        Self::Disabled
98    }
99
100    /// Create an enabled flag from a CLI argument.
101    pub 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 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 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 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 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 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 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 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    } = build_args;
637
638    ResolverOptions {
639        indexes: index_args.resolve(configured_indexes)?,
640        upgrade: Upgrade::from_args(
641            flag(upgrade, no_upgrade, "upgrade")?,
642            upgrade_package.into_iter().map(Requirement::from).collect(),
643            upgrade_group,
644        ),
645        index_strategy,
646        keyring_provider,
647        resolution,
648        prerelease: if pre {
649            Some(PrereleaseMode::Allow)
650        } else {
651            prerelease
652        },
653        prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
654        fork_strategy,
655        dependency_metadata: None,
656        config_settings: config_setting
657            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
658        config_settings_package: config_settings_package.map(|config_settings| {
659            config_settings
660                .into_iter()
661                .collect::<PackageConfigSettings>()
662        }),
663        build_isolation: BuildIsolation::from_args(
664            flag(no_build_isolation, build_isolation, "build-isolation")?,
665            no_build_isolation_package,
666        ),
667        extra_build_dependencies: None,
668        extra_build_variables: None,
669        exclude_newer,
670        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
671        link_mode,
672        torch_backend: None,
673        no_build: flag(no_build, build, "build")?,
674        no_build_package: if no_build_package.is_empty() {
675            None
676        } else {
677            Some(no_build_package)
678        },
679        no_binary: flag(no_binary, binary, "binary")?,
680        no_binary_package: if no_binary_package.is_empty() {
681            None
682        } else {
683            Some(no_binary_package)
684        },
685        no_sources: if no_sources { Some(true) } else { None },
686        no_sources_package: if no_sources_package.is_empty() {
687            None
688        } else {
689            Some(no_sources_package)
690        },
691    }
692    .relative_to(&env::current_dir()?)
693    .map_err(Into::into)
694}
695
696/// Construct the [`ResolverOptions`] for an [`UpgradeArgs`] invocation.
697pub fn upgrade_options(
698    args: UpgradeArgs,
699    configured_indexes: &[Index],
700) -> anyhow::Result<(Vec<PackageName>, Vec<PackageName>, ResolverOptions)> {
701    let UpgradeArgs {
702        packages,
703        exclude,
704        index_args,
705        registry_client:
706            RegistryClientArgs {
707                index_strategy,
708                keyring_provider,
709            },
710    } = args;
711
712    let options = ResolverOptions {
713        indexes: index_args.resolve(configured_indexes)?,
714        index_strategy,
715        keyring_provider,
716        ..ResolverOptions::default()
717    }
718    .relative_to(&env::current_dir()?)?;
719
720    Ok((packages, exclude, options))
721}
722
723/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildOptionsArgs`].
724pub fn resolver_installer_options(
725    resolver_installer_args: ResolverInstallerArgs,
726    build_args: BuildOptionsArgs,
727    configured_indexes: &[Index],
728) -> anyhow::Result<ResolverInstallerOptions> {
729    let ResolverInstallerArgs {
730        index_args,
731        upgrade,
732        no_upgrade,
733        upgrade_package,
734        upgrade_group,
735        reinstall:
736            ReinstallArgs {
737                reinstall,
738                no_reinstall,
739                reinstall_package,
740            },
741        registry_client:
742            RegistryClientArgs {
743                index_strategy,
744                keyring_provider,
745            },
746        version_selection:
747            VersionSelectionArgs {
748                resolution,
749                prerelease,
750                prerelease_package,
751                pre,
752                fork_strategy,
753            },
754        config_setting,
755        config_settings_package,
756        build_isolation:
757            PackageBuildIsolationArgs {
758                build_isolation:
759                    BuildIsolationArgs {
760                        no_build_isolation,
761                        build_isolation,
762                    },
763                no_build_isolation_package,
764            },
765        exclude_newer:
766            PackageExcludeNewerArgs {
767                exclude_newer: ExcludeNewerArgs { exclude_newer },
768                exclude_newer_package,
769            },
770        link_mode,
771        compile_bytecode:
772            CompileBytecodeArgs {
773                compile_bytecode,
774                no_compile_bytecode,
775            },
776        sources: SourcesArgs {
777            no_sources,
778            no_sources_package,
779        },
780    } = resolver_installer_args;
781
782    let BuildOptionsArgs {
783        no_build,
784        build,
785        no_build_package,
786        no_binary,
787        binary,
788        no_binary_package,
789    } = build_args;
790
791    ResolverInstallerOptions {
792        indexes: index_args.resolve(configured_indexes)?,
793        upgrade: Upgrade::from_args(
794            flag(upgrade, no_upgrade, "upgrade")?,
795            upgrade_package.into_iter().map(Requirement::from).collect(),
796            upgrade_group,
797        ),
798        reinstall: Reinstall::from_args(
799            flag(reinstall, no_reinstall, "reinstall")?,
800            reinstall_package,
801        ),
802        index_strategy,
803        keyring_provider,
804        resolution,
805        prerelease: if pre {
806            Some(PrereleaseMode::Allow)
807        } else {
808            prerelease
809        },
810        prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
811        fork_strategy,
812        dependency_metadata: None,
813        config_settings: config_setting
814            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
815        config_settings_package: config_settings_package.map(|config_settings| {
816            config_settings
817                .into_iter()
818                .collect::<PackageConfigSettings>()
819        }),
820        build_isolation: BuildIsolation::from_args(
821            flag(no_build_isolation, build_isolation, "build-isolation")?,
822            no_build_isolation_package,
823        ),
824        extra_build_dependencies: None,
825        extra_build_variables: None,
826        exclude_newer,
827        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
828        link_mode,
829        compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
830        no_build: flag(no_build, build, "build")?,
831        no_build_package: if no_build_package.is_empty() {
832            None
833        } else {
834            Some(no_build_package)
835        },
836        no_binary: flag(no_binary, binary, "binary")?,
837        no_binary_package: if no_binary_package.is_empty() {
838            None
839        } else {
840            Some(no_binary_package)
841        },
842        no_sources: if no_sources { Some(true) } else { None },
843        no_sources_package: if no_sources_package.is_empty() {
844            None
845        } else {
846            Some(no_sources_package)
847        },
848        torch_backend: None,
849    }
850    .relative_to(&env::current_dir()?)
851    .map_err(Into::into)
852}