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