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, 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
243impl TryFrom<ResolverArgs> for PipOptions {
244    type Error = anyhow::Error;
245
246    fn try_from(args: ResolverArgs) -> anyhow::Result<Self> {
247        let ResolverArgs {
248            index_args,
249            upgrade,
250            no_upgrade,
251            upgrade_package,
252            upgrade_group,
253            registry_client:
254                RegistryClientArgs {
255                    index_strategy,
256                    keyring_provider,
257                },
258            version_selection:
259                VersionSelectionArgs {
260                    resolution,
261                    prerelease,
262                    prerelease_package,
263                    pre,
264                    fork_strategy,
265                },
266            config_setting,
267            config_settings_package,
268            build_isolation:
269                PackageBuildIsolationArgs {
270                    build_isolation:
271                        BuildIsolationArgs {
272                            no_build_isolation,
273                            build_isolation,
274                        },
275                    no_build_isolation_package,
276                },
277            exclude_newer:
278                PackageExcludeNewerArgs {
279                    exclude_newer: ExcludeNewerArgs { exclude_newer },
280                    exclude_newer_package,
281                },
282            link_mode,
283            sources:
284                SourcesArgs {
285                    no_sources,
286                    no_sources_package,
287                },
288        } = args;
289
290        if !upgrade_group.is_empty() {
291            bail!(ArgumentError(format!(
292                "`{}` is not supported in `uv pip` commands",
293                "--upgrade-group".green()
294            )));
295        }
296
297        Ok(Self {
298            upgrade: flag(upgrade, no_upgrade, "upgrade")?,
299            upgrade_package: Some(upgrade_package),
300            index_strategy,
301            keyring_provider,
302            resolution,
303            fork_strategy,
304            prerelease: if pre {
305                Some(PrereleaseMode::Allow)
306            } else {
307                prerelease
308            },
309            prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
310            config_settings: config_setting
311                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
312            config_settings_package: config_settings_package.map(|config_settings| {
313                config_settings
314                    .into_iter()
315                    .collect::<PackageConfigSettings>()
316            }),
317            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
318            no_build_isolation_package: Some(no_build_isolation_package),
319            exclude_newer,
320            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
321            link_mode,
322            no_sources: if no_sources { Some(true) } else { None },
323            no_sources_package: if no_sources_package.is_empty() {
324                None
325            } else {
326                Some(no_sources_package)
327            },
328            ..Self::try_from(index_args)?
329        })
330    }
331}
332
333impl TryFrom<InstallerArgs> for PipOptions {
334    type Error = anyhow::Error;
335
336    fn try_from(args: InstallerArgs) -> anyhow::Result<Self> {
337        let InstallerArgs {
338            index_args,
339            reinstall:
340                ReinstallArgs {
341                    reinstall,
342                    no_reinstall,
343                    reinstall_package,
344                },
345            registry_client:
346                RegistryClientArgs {
347                    index_strategy,
348                    keyring_provider,
349                },
350            config_setting,
351            config_settings_package,
352            build_isolation:
353                BuildIsolationArgs {
354                    no_build_isolation,
355                    build_isolation,
356                },
357            exclude_newer:
358                PackageExcludeNewerArgs {
359                    exclude_newer: ExcludeNewerArgs { exclude_newer },
360                    exclude_newer_package,
361                },
362            link_mode,
363            compile_bytecode:
364                CompileBytecodeArgs {
365                    compile_bytecode,
366                    no_compile_bytecode,
367                },
368            sources:
369                SourcesArgs {
370                    no_sources,
371                    no_sources_package,
372                },
373        } = args;
374
375        Ok(Self {
376            reinstall: flag(reinstall, no_reinstall, "reinstall")?,
377            reinstall_package: Some(reinstall_package),
378            index_strategy,
379            keyring_provider,
380            config_settings: config_setting
381                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
382            config_settings_package: config_settings_package.map(|config_settings| {
383                config_settings
384                    .into_iter()
385                    .collect::<PackageConfigSettings>()
386            }),
387            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
388            exclude_newer,
389            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
390            link_mode,
391            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
392            no_sources: if no_sources { Some(true) } else { None },
393            no_sources_package: if no_sources_package.is_empty() {
394                None
395            } else {
396                Some(no_sources_package)
397            },
398            ..Self::try_from(index_args)?
399        })
400    }
401}
402
403impl TryFrom<ResolverInstallerArgs> for PipOptions {
404    type Error = anyhow::Error;
405
406    fn try_from(args: ResolverInstallerArgs) -> anyhow::Result<Self> {
407        let ResolverInstallerArgs {
408            index_args,
409            upgrade,
410            no_upgrade,
411            upgrade_package,
412            upgrade_group,
413            reinstall:
414                ReinstallArgs {
415                    reinstall,
416                    no_reinstall,
417                    reinstall_package,
418                },
419            registry_client:
420                RegistryClientArgs {
421                    index_strategy,
422                    keyring_provider,
423                },
424            version_selection:
425                VersionSelectionArgs {
426                    resolution,
427                    prerelease,
428                    prerelease_package,
429                    pre,
430                    fork_strategy,
431                },
432            config_setting,
433            config_settings_package,
434            build_isolation:
435                PackageBuildIsolationArgs {
436                    build_isolation:
437                        BuildIsolationArgs {
438                            no_build_isolation,
439                            build_isolation,
440                        },
441                    no_build_isolation_package,
442                },
443            exclude_newer:
444                PackageExcludeNewerArgs {
445                    exclude_newer: ExcludeNewerArgs { exclude_newer },
446                    exclude_newer_package,
447                },
448            link_mode,
449            compile_bytecode:
450                CompileBytecodeArgs {
451                    compile_bytecode,
452                    no_compile_bytecode,
453                },
454            sources:
455                SourcesArgs {
456                    no_sources,
457                    no_sources_package,
458                },
459        } = args;
460
461        if !upgrade_group.is_empty() {
462            bail!(ArgumentError(format!(
463                "`{}` is not supported in `uv pip` commands",
464                "--upgrade-group".green()
465            )));
466        }
467
468        Ok(Self {
469            upgrade: flag(upgrade, no_upgrade, "upgrade")?,
470            upgrade_package: Some(upgrade_package),
471            reinstall: flag(reinstall, no_reinstall, "reinstall")?,
472            reinstall_package: Some(reinstall_package),
473            index_strategy,
474            keyring_provider,
475            resolution,
476            prerelease: if pre {
477                Some(PrereleaseMode::Allow)
478            } else {
479                prerelease
480            },
481            prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
482            fork_strategy,
483            config_settings: config_setting
484                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
485            config_settings_package: config_settings_package.map(|config_settings| {
486                config_settings
487                    .into_iter()
488                    .collect::<PackageConfigSettings>()
489            }),
490            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation")?,
491            no_build_isolation_package: Some(no_build_isolation_package),
492            exclude_newer,
493            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
494            link_mode,
495            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
496            no_sources: if no_sources { Some(true) } else { None },
497            no_sources_package: if no_sources_package.is_empty() {
498                None
499            } else {
500                Some(no_sources_package)
501            },
502            ..Self::try_from(index_args)?
503        })
504    }
505}
506
507impl TryFrom<FetchArgs> for PipOptions {
508    type Error = anyhow::Error;
509
510    fn try_from(args: FetchArgs) -> anyhow::Result<Self> {
511        let FetchArgs {
512            index_args,
513            registry_client:
514                RegistryClientArgs {
515                    index_strategy,
516                    keyring_provider,
517                },
518            exclude_newer:
519                PackageExcludeNewerArgs {
520                    exclude_newer: ExcludeNewerArgs { exclude_newer },
521                    exclude_newer_package,
522                },
523        } = args;
524
525        Ok(Self {
526            index_strategy,
527            keyring_provider,
528            exclude_newer,
529            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
530            ..Self::try_from(index_args)?
531        })
532    }
533}
534
535impl IndexArgs {
536    /// Resolve the index arguments shared by pip, resolver, and installer settings.
537    fn resolve(self) -> IndexOptions {
538        let Self {
539            default_index,
540            index,
541            index_url,
542            extra_index_url,
543            no_index,
544            find_links,
545        } = self;
546
547        let default_index = default_index
548            .and_then(Maybe::into_option)
549            .map(|index| vec![index]);
550        let index = index.map(|indexes| {
551            indexes
552                .into_iter()
553                .flatten()
554                .filter_map(Maybe::into_option)
555                .collect()
556        });
557
558        IndexOptions {
559            index: default_index.combine(index),
560            index_url: index_url.and_then(Maybe::into_option),
561            extra_index_url: extra_index_url
562                .map(|indexes| indexes.into_iter().filter_map(Maybe::into_option).collect()),
563            no_index: no_index.then_some(true),
564            find_links: find_links
565                .map(|links| links.into_iter().filter_map(Maybe::into_option).collect()),
566        }
567    }
568}
569
570impl TryFrom<IndexArgs> for PipOptions {
571    type Error = anyhow::Error;
572
573    fn try_from(args: IndexArgs) -> anyhow::Result<Self> {
574        Ok(Self::from(
575            args.resolve().relative_to(&env::current_dir()?)?,
576        ))
577    }
578}
579
580/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildOptionsArgs`].
581pub fn resolver_options(
582    resolver_args: ResolverArgs,
583    build_args: BuildOptionsArgs,
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(),
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) -> anyhow::Result<ResolverInstallerOptions> {
699    let ResolverInstallerArgs {
700        index_args,
701        upgrade,
702        no_upgrade,
703        upgrade_package,
704        upgrade_group,
705        reinstall:
706            ReinstallArgs {
707                reinstall,
708                no_reinstall,
709                reinstall_package,
710            },
711        registry_client:
712            RegistryClientArgs {
713                index_strategy,
714                keyring_provider,
715            },
716        version_selection:
717            VersionSelectionArgs {
718                resolution,
719                prerelease,
720                prerelease_package,
721                pre,
722                fork_strategy,
723            },
724        config_setting,
725        config_settings_package,
726        build_isolation:
727            PackageBuildIsolationArgs {
728                build_isolation:
729                    BuildIsolationArgs {
730                        no_build_isolation,
731                        build_isolation,
732                    },
733                no_build_isolation_package,
734            },
735        exclude_newer:
736            PackageExcludeNewerArgs {
737                exclude_newer: ExcludeNewerArgs { exclude_newer },
738                exclude_newer_package,
739            },
740        link_mode,
741        compile_bytecode:
742            CompileBytecodeArgs {
743                compile_bytecode,
744                no_compile_bytecode,
745            },
746        sources: SourcesArgs {
747            no_sources,
748            no_sources_package,
749        },
750    } = resolver_installer_args;
751
752    let BuildOptionsArgs {
753        no_build,
754        build,
755        no_build_package,
756        no_binary,
757        binary,
758        no_binary_package,
759    } = build_args;
760
761    ResolverInstallerOptions {
762        indexes: index_args.resolve(),
763        upgrade: Upgrade::from_args(
764            flag(upgrade, no_upgrade, "upgrade")?,
765            upgrade_package.into_iter().map(Requirement::from).collect(),
766            upgrade_group,
767        ),
768        reinstall: Reinstall::from_args(
769            flag(reinstall, no_reinstall, "reinstall")?,
770            reinstall_package,
771        ),
772        index_strategy,
773        keyring_provider,
774        resolution,
775        prerelease: if pre {
776            Some(PrereleaseMode::Allow)
777        } else {
778            prerelease
779        },
780        prerelease_package: prerelease_package.map(PrereleasePackage::from_iter),
781        fork_strategy,
782        dependency_metadata: None,
783        config_settings: config_setting
784            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
785        config_settings_package: config_settings_package.map(|config_settings| {
786            config_settings
787                .into_iter()
788                .collect::<PackageConfigSettings>()
789        }),
790        build_isolation: BuildIsolation::from_args(
791            flag(no_build_isolation, build_isolation, "build-isolation")?,
792            no_build_isolation_package,
793        ),
794        extra_build_dependencies: None,
795        extra_build_variables: None,
796        exclude_newer,
797        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
798        link_mode,
799        compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode")?,
800        no_build: flag(no_build, build, "build")?,
801        no_build_package: if no_build_package.is_empty() {
802            None
803        } else {
804            Some(no_build_package)
805        },
806        no_binary: flag(no_binary, binary, "binary")?,
807        no_binary_package: if no_binary_package.is_empty() {
808            None
809        } else {
810            Some(no_binary_package)
811        },
812        no_sources: if no_sources { Some(true) } else { None },
813        no_sources_package: if no_sources_package.is_empty() {
814            None
815        } else {
816            Some(no_sources_package)
817        },
818        torch_backend: None,
819    }
820    .relative_to(&env::current_dir()?)
821    .map_err(Into::into)
822}