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