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        no_build: flag(no_build, build, "build"),
370        no_build_package: Some(no_build_package),
371        no_binary: flag(no_binary, binary, "binary"),
372        no_binary_package: Some(no_binary_package),
373        no_sources: if no_sources { Some(true) } else { None },
374    }
375}
376
377/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildOptionsArgs`].
378pub fn resolver_installer_options(
379    resolver_installer_args: ResolverInstallerArgs,
380    build_args: BuildOptionsArgs,
381) -> ResolverInstallerOptions {
382    let ResolverInstallerArgs {
383        index_args,
384        upgrade,
385        no_upgrade,
386        upgrade_package,
387        reinstall,
388        no_reinstall,
389        reinstall_package,
390        index_strategy,
391        keyring_provider,
392        resolution,
393        prerelease,
394        pre,
395        fork_strategy,
396        config_setting,
397        config_settings_package,
398        no_build_isolation,
399        no_build_isolation_package,
400        build_isolation,
401        exclude_newer,
402        exclude_newer_package,
403        link_mode,
404        compile_bytecode,
405        no_compile_bytecode,
406        no_sources,
407    } = resolver_installer_args;
408
409    let BuildOptionsArgs {
410        no_build,
411        build,
412        no_build_package,
413        no_binary,
414        binary,
415        no_binary_package,
416    } = build_args;
417
418    let default_index = index_args
419        .default_index
420        .and_then(Maybe::into_option)
421        .map(|default_index| vec![default_index]);
422    let index = index_args.index.map(|index| {
423        index
424            .into_iter()
425            .flat_map(|v| v.clone())
426            .filter_map(Maybe::into_option)
427            .collect()
428    });
429
430    ResolverInstallerOptions {
431        index: default_index.combine(index),
432        index_url: index_args.index_url.and_then(Maybe::into_option),
433        extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
434            extra_index_url
435                .into_iter()
436                .filter_map(Maybe::into_option)
437                .collect()
438        }),
439        no_index: if index_args.no_index {
440            Some(true)
441        } else {
442            None
443        },
444        find_links: index_args.find_links.map(|find_links| {
445            find_links
446                .into_iter()
447                .filter_map(Maybe::into_option)
448                .collect()
449        }),
450        upgrade: Upgrade::from_args(
451            flag(upgrade, no_upgrade, "upgrade"),
452            upgrade_package.into_iter().map(Requirement::from).collect(),
453        ),
454        reinstall: Reinstall::from_args(
455            flag(reinstall, no_reinstall, "reinstall"),
456            reinstall_package,
457        ),
458        index_strategy,
459        keyring_provider,
460        resolution,
461        prerelease: if pre {
462            Some(PrereleaseMode::Allow)
463        } else {
464            prerelease
465        },
466        fork_strategy,
467        dependency_metadata: None,
468        config_settings: config_setting
469            .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
470        config_settings_package: config_settings_package.map(|config_settings| {
471            config_settings
472                .into_iter()
473                .collect::<PackageConfigSettings>()
474        }),
475        build_isolation: BuildIsolation::from_args(
476            flag(no_build_isolation, build_isolation, "build-isolation"),
477            no_build_isolation_package,
478        ),
479        extra_build_dependencies: None,
480        extra_build_variables: None,
481        exclude_newer,
482        exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
483        link_mode,
484        compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
485        no_build: flag(no_build, build, "build"),
486        no_build_package: if no_build_package.is_empty() {
487            None
488        } else {
489            Some(no_build_package)
490        },
491        no_binary: flag(no_binary, binary, "binary"),
492        no_binary_package: if no_binary_package.is_empty() {
493            None
494        } else {
495            Some(no_binary_package)
496        },
497        no_sources: if no_sources { Some(true) } else { None },
498    }
499}