uv_cli/
options.rs

1use anstream::eprintln;
2
3use uv_cache::Refresh;
4use uv_configuration::{BuildIsolation, Reinstall, Upgrade};
5use uv_distribution_types::{ConfigSettings, PackageConfigSettings, Requirement};
6use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
7use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
8use uv_warnings::owo_colors::OwoColorize;
9
10use crate::{
11    BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
12    ResolverInstallerArgs,
13};
14
15/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
16pub fn flag(yes: bool, no: bool, name: &str) -> Option<bool> {
17    match (yes, no) {
18        (true, false) => Some(true),
19        (false, true) => Some(false),
20        (false, false) => None,
21        (..) => {
22            eprintln!(
23                "{}{} `{}` and `{}` cannot be used together. \
24                Boolean flags on different levels are currently not supported \
25                (https://github.com/clap-rs/clap/issues/6049)",
26                "error".bold().red(),
27                ":".bold(),
28                format!("--{name}").green(),
29                format!("--no-{name}").green(),
30            );
31            // No error forwarding since should eventually be solved on the clap side.
32            #[allow(clippy::exit)]
33            {
34                std::process::exit(2);
35            }
36        }
37    }
38}
39
40impl From<RefreshArgs> for Refresh {
41    fn from(value: RefreshArgs) -> Self {
42        let RefreshArgs {
43            refresh,
44            no_refresh,
45            refresh_package,
46        } = value;
47
48        Self::from_args(flag(refresh, no_refresh, "no-refresh"), refresh_package)
49    }
50}
51
52impl From<ResolverArgs> for PipOptions {
53    fn from(args: ResolverArgs) -> Self {
54        let ResolverArgs {
55            index_args,
56            upgrade,
57            no_upgrade,
58            upgrade_package,
59            index_strategy,
60            keyring_provider,
61            resolution,
62            prerelease,
63            pre,
64            fork_strategy,
65            config_setting,
66            config_settings_package,
67            no_build_isolation,
68            no_build_isolation_package,
69            build_isolation,
70            exclude_newer,
71            link_mode,
72            no_sources,
73            exclude_newer_package,
74        } = args;
75
76        Self {
77            upgrade: flag(upgrade, no_upgrade, "no-upgrade"),
78            upgrade_package: Some(upgrade_package),
79            index_strategy,
80            keyring_provider,
81            resolution,
82            fork_strategy,
83            prerelease: if pre {
84                Some(PrereleaseMode::Allow)
85            } else {
86                prerelease
87            },
88            config_settings: config_setting
89                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
90            config_settings_package: config_settings_package.map(|config_settings| {
91                config_settings
92                    .into_iter()
93                    .collect::<PackageConfigSettings>()
94            }),
95            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
96            no_build_isolation_package: Some(no_build_isolation_package),
97            exclude_newer,
98            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
99            link_mode,
100            no_sources: if no_sources { Some(true) } else { None },
101            ..Self::from(index_args)
102        }
103    }
104}
105
106impl From<InstallerArgs> for PipOptions {
107    fn from(args: InstallerArgs) -> Self {
108        let InstallerArgs {
109            index_args,
110            reinstall,
111            no_reinstall,
112            reinstall_package,
113            index_strategy,
114            keyring_provider,
115            config_setting,
116            config_settings_package,
117            no_build_isolation,
118            build_isolation,
119            exclude_newer,
120            link_mode,
121            compile_bytecode,
122            no_compile_bytecode,
123            no_sources,
124            exclude_newer_package,
125        } = args;
126
127        Self {
128            reinstall: flag(reinstall, no_reinstall, "reinstall"),
129            reinstall_package: Some(reinstall_package),
130            index_strategy,
131            keyring_provider,
132            config_settings: config_setting
133                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
134            config_settings_package: config_settings_package.map(|config_settings| {
135                config_settings
136                    .into_iter()
137                    .collect::<PackageConfigSettings>()
138            }),
139            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
140            exclude_newer,
141            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
142            link_mode,
143            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
144            no_sources: if no_sources { Some(true) } else { None },
145            ..Self::from(index_args)
146        }
147    }
148}
149
150impl From<ResolverInstallerArgs> for PipOptions {
151    fn from(args: ResolverInstallerArgs) -> Self {
152        let ResolverInstallerArgs {
153            index_args,
154            upgrade,
155            no_upgrade,
156            upgrade_package,
157            reinstall,
158            no_reinstall,
159            reinstall_package,
160            index_strategy,
161            keyring_provider,
162            resolution,
163            prerelease,
164            pre,
165            fork_strategy,
166            config_setting,
167            config_settings_package,
168            no_build_isolation,
169            no_build_isolation_package,
170            build_isolation,
171            exclude_newer,
172            link_mode,
173            compile_bytecode,
174            no_compile_bytecode,
175            no_sources,
176            exclude_newer_package,
177        } = args;
178
179        Self {
180            upgrade: flag(upgrade, no_upgrade, "upgrade"),
181            upgrade_package: Some(upgrade_package),
182            reinstall: flag(reinstall, no_reinstall, "reinstall"),
183            reinstall_package: Some(reinstall_package),
184            index_strategy,
185            keyring_provider,
186            resolution,
187            prerelease: if pre {
188                Some(PrereleaseMode::Allow)
189            } else {
190                prerelease
191            },
192            fork_strategy,
193            config_settings: config_setting
194                .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
195            config_settings_package: config_settings_package.map(|config_settings| {
196                config_settings
197                    .into_iter()
198                    .collect::<PackageConfigSettings>()
199            }),
200            no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
201            no_build_isolation_package: Some(no_build_isolation_package),
202            exclude_newer,
203            exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
204            link_mode,
205            compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
206            no_sources: if no_sources { Some(true) } else { None },
207            ..Self::from(index_args)
208        }
209    }
210}
211
212impl From<FetchArgs> for PipOptions {
213    fn from(args: FetchArgs) -> Self {
214        let FetchArgs {
215            index_args,
216            index_strategy,
217            keyring_provider,
218            exclude_newer,
219        } = args;
220
221        Self {
222            index_strategy,
223            keyring_provider,
224            exclude_newer,
225            ..Self::from(index_args)
226        }
227    }
228}
229
230impl From<IndexArgs> for PipOptions {
231    fn from(args: IndexArgs) -> Self {
232        let IndexArgs {
233            default_index,
234            index,
235            index_url,
236            extra_index_url,
237            no_index,
238            find_links,
239        } = args;
240
241        Self {
242            index: default_index
243                .and_then(Maybe::into_option)
244                .map(|default_index| vec![default_index])
245                .combine(index.map(|index| {
246                    index
247                        .iter()
248                        .flat_map(std::clone::Clone::clone)
249                        .filter_map(Maybe::into_option)
250                        .collect()
251                })),
252            index_url: index_url.and_then(Maybe::into_option),
253            extra_index_url: extra_index_url.map(|extra_index_urls| {
254                extra_index_urls
255                    .into_iter()
256                    .filter_map(Maybe::into_option)
257                    .collect()
258            }),
259            no_index: if no_index { Some(true) } else { None },
260            find_links: find_links.map(|find_links| {
261                find_links
262                    .into_iter()
263                    .filter_map(Maybe::into_option)
264                    .collect()
265            }),
266            ..Self::default()
267        }
268    }
269}
270
271/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildOptionsArgs`].
272pub fn resolver_options(
273    resolver_args: ResolverArgs,
274    build_args: BuildOptionsArgs,
275) -> ResolverOptions {
276    let ResolverArgs {
277        index_args,
278        upgrade,
279        no_upgrade,
280        upgrade_package,
281        index_strategy,
282        keyring_provider,
283        resolution,
284        prerelease,
285        pre,
286        fork_strategy,
287        config_setting,
288        config_settings_package,
289        no_build_isolation,
290        no_build_isolation_package,
291        build_isolation,
292        exclude_newer,
293        link_mode,
294        no_sources,
295        exclude_newer_package,
296    } = resolver_args;
297
298    let BuildOptionsArgs {
299        no_build,
300        build,
301        no_build_package,
302        no_binary,
303        binary,
304        no_binary_package,
305    } = build_args;
306
307    ResolverOptions {
308        index: index_args
309            .default_index
310            .and_then(Maybe::into_option)
311            .map(|default_index| vec![default_index])
312            .combine(index_args.index.map(|index| {
313                index
314                    .into_iter()
315                    .flat_map(|v| v.clone())
316                    .filter_map(Maybe::into_option)
317                    .collect()
318            })),
319        index_url: index_args.index_url.and_then(Maybe::into_option),
320        extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
321            extra_index_url
322                .into_iter()
323                .filter_map(Maybe::into_option)
324                .collect()
325        }),
326        no_index: if index_args.no_index {
327            Some(true)
328        } else {
329            None
330        },
331        find_links: index_args.find_links.map(|find_links| {
332            find_links
333                .into_iter()
334                .filter_map(Maybe::into_option)
335                .collect()
336        }),
337        upgrade: Upgrade::from_args(
338            flag(upgrade, no_upgrade, "no-upgrade"),
339            upgrade_package.into_iter().map(Requirement::from).collect(),
340        ),
341        index_strategy,
342        keyring_provider,
343        resolution,
344        prerelease: if pre {
345            Some(PrereleaseMode::Allow)
346        } else {
347            prerelease
348        },
349        fork_strategy,
350        dependency_metadata: None,
351        config_settings: config_setting
352            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
353        config_settings_package: config_settings_package.map(|config_settings| {
354            config_settings
355                .into_iter()
356                .collect::<PackageConfigSettings>()
357        }),
358        build_isolation: BuildIsolation::from_args(
359            flag(no_build_isolation, build_isolation, "build-isolation"),
360            no_build_isolation_package,
361        ),
362        extra_build_dependencies: None,
363        extra_build_variables: None,
364        exclude_newer: ExcludeNewer::from_args(
365            exclude_newer,
366            exclude_newer_package.unwrap_or_default(),
367        ),
368        link_mode,
369        torch_backend: None,
370        no_build: flag(no_build, build, "build"),
371        no_build_package: Some(no_build_package),
372        no_binary: flag(no_binary, binary, "binary"),
373        no_binary_package: Some(no_binary_package),
374        no_sources: if no_sources { Some(true) } else { None },
375    }
376}
377
378/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildOptionsArgs`].
379pub fn resolver_installer_options(
380    resolver_installer_args: ResolverInstallerArgs,
381    build_args: BuildOptionsArgs,
382) -> ResolverInstallerOptions {
383    let ResolverInstallerArgs {
384        index_args,
385        upgrade,
386        no_upgrade,
387        upgrade_package,
388        reinstall,
389        no_reinstall,
390        reinstall_package,
391        index_strategy,
392        keyring_provider,
393        resolution,
394        prerelease,
395        pre,
396        fork_strategy,
397        config_setting,
398        config_settings_package,
399        no_build_isolation,
400        no_build_isolation_package,
401        build_isolation,
402        exclude_newer,
403        exclude_newer_package,
404        link_mode,
405        compile_bytecode,
406        no_compile_bytecode,
407        no_sources,
408    } = resolver_installer_args;
409
410    let BuildOptionsArgs {
411        no_build,
412        build,
413        no_build_package,
414        no_binary,
415        binary,
416        no_binary_package,
417    } = build_args;
418
419    let default_index = index_args
420        .default_index
421        .and_then(Maybe::into_option)
422        .map(|default_index| vec![default_index]);
423    let index = index_args.index.map(|index| {
424        index
425            .into_iter()
426            .flat_map(|v| v.clone())
427            .filter_map(Maybe::into_option)
428            .collect()
429    });
430
431    ResolverInstallerOptions {
432        index: default_index.combine(index),
433        index_url: index_args.index_url.and_then(Maybe::into_option),
434        extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
435            extra_index_url
436                .into_iter()
437                .filter_map(Maybe::into_option)
438                .collect()
439        }),
440        no_index: if index_args.no_index {
441            Some(true)
442        } else {
443            None
444        },
445        find_links: index_args.find_links.map(|find_links| {
446            find_links
447                .into_iter()
448                .filter_map(Maybe::into_option)
449                .collect()
450        }),
451        upgrade: Upgrade::from_args(
452            flag(upgrade, no_upgrade, "upgrade"),
453            upgrade_package.into_iter().map(Requirement::from).collect(),
454        ),
455        reinstall: Reinstall::from_args(
456            flag(reinstall, no_reinstall, "reinstall"),
457            reinstall_package,
458        ),
459        index_strategy,
460        keyring_provider,
461        resolution,
462        prerelease: if pre {
463            Some(PrereleaseMode::Allow)
464        } else {
465            prerelease
466        },
467        fork_strategy,
468        dependency_metadata: None,
469        config_settings: config_setting
470            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
471        config_settings_package: config_settings_package.map(|config_settings| {
472            config_settings
473                .into_iter()
474                .collect::<PackageConfigSettings>()
475        }),
476        build_isolation: BuildIsolation::from_args(
477            flag(no_build_isolation, build_isolation, "build-isolation"),
478            no_build_isolation_package,
479        ),
480        extra_build_dependencies: None,
481        extra_build_variables: None,
482        exclude_newer,
483        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
484        link_mode,
485        compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
486        no_build: flag(no_build, build, "build"),
487        no_build_package: if no_build_package.is_empty() {
488            None
489        } else {
490            Some(no_build_package)
491        },
492        no_binary: flag(no_binary, binary, "binary"),
493        no_binary_package: if no_binary_package.is_empty() {
494            None
495        } else {
496            Some(no_binary_package)
497        },
498        no_sources: if no_sources { Some(true) } else { None },
499        torch_backend: None,
500    }
501}