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